周末折腾了好久,终于成功把拉钩网的招聘信息爬取下来了。现在总结一下!

环境: windows 8.1 + python 3.5.0


首先使用 scrapy 创建一个项目:

E:\mypy> scrapy startproject lgjob


创建后目录结构:

E:\mypy\lgjob
----scrapy.cfg
----lgjob
     |-------__pycache__
     |-------spiders
          |-------__pycache__
          |-------__init__.py
     |-------__init__.py
     |-------items.py
     |-------middlewares.py
     |-------pipelines.py
     |-------settings.py


网络上有一中方法是读取 json 格式的,如下图:

Python scrapy 爬取拉勾网招聘信息_html


这种方法是比较完整的,每个公司的招聘信息都很完整。拉钩网默认每页显示15个公司,最大30页。json格式比较规范,但是测试过程中一直读取不到网页记录,提示 “ 操作频繁 ” 。所以打算用传统的方法,即找出每页的规律,确认页码的位置。而内容则是直接读取 html 格式的节点取记录。


我们从拉钩首页点击某一个分类,比如我点击了 “java” ,接下来就跳转到各个公司的招聘信息列表,拉钩每页显示15个公司。看看网址为 : https://www.lagou.com/zhaopin/Java/?labelWords=label,网址中看到关键字 “java” 了吗?替换成 “DBA” 回车查询也是可以的。再点击分页,也发现分页页码显示规律了!

如下为搜索 DBA 的第二页 和第五页: 

​https://www.lagou.com/zhaopin/DBA/2/?filterOption=3​

​https://www.lagou.com/zhaopin/DBA/5/?filterOption=3​


按 F12 再右键某个职位名称:

Python scrapy 爬取拉勾网招聘信息_ci_02


每页15个公司的招聘信息都显示出来了,这里不需要要查看职位的详细信息,所以在查找职位页面就可以把基础的信息爬取下来。职位在列表标签 “li” 显示是有规律和固定格式的,所以等下爬取也是每页去爬取,每页循环读取网页标签 “<li class="con_list_item default_list"…………” 的各个元素记录。


主要的项目文件,默认都已经创建了:(E:\mypy\lgjob\lgjob\)

items.py : 爬取的主要目标就是从非结构性的数据源提取结构性数据,例如网页。 Scrapy提供 Item 类来满足这样的需求。

pipelines.py :  当Item在Spider中被收集之后,它将会被传递到Item Pipeline,一些组件会按照一定的顺序执行对Item的处理(如保持到数据库)。

settings.py : 设定为代码提供了提取以key-value映射的配置值的的全局命名空间(namespace)。 设定可以通过下面介绍的多种机制进行设置。


还有一个主要的解析主文件,需要手动创建,放到项目的 spiders (E:\mypy\lgjob\lgjob\spiders)目录中,本次测试创建的文件名为 : main.py


现在数据库创建一个表,本次测试使用的是 sql server 数据库保存爬取数据:

CREATE TABLE [dbo].[lgjob](
[companyfullname] [varchar](50) NULL,
[positionname] [varchar](30) NULL,
[salary] [varchar](20) NULL,
[workyear] [varchar](20) NULL,
[education] [varchar](20) NULL,
[city] [varchar](20) NULL,
[district] [varchar](20) NULL,
[financestage] [varchar](50) NULL,
[industryfield] [varchar](100) NULL,
[firsttype] [varchar](50) NULL,
[positionlables] [varchar](100) NULL
) ON [PRIMARY]
GO

【items.py】 

# -*- coding: utf-8 -*-
# python 3.5

import scrapy

class LgjobItem(scrapy.Item):
companyfullname = scrapy.Field()
positionname = scrapy.Field()
salary = scrapy.Field()
workyear = scrapy.Field()
education = scrapy.Field()
city = scrapy.Field()
district = scrapy.Field()
financestage = scrapy.Field()
industryfield = scrapy.Field()
firsttype = scrapy.Field()
positionlables = scrapy.Field()

【pipelines.py】结果保持到数据库

# -*- coding: utf-8 -*-
# python 3.5

import pymssql
from scrapy.conf import settings

class LgjobPipeline(object):

def __init__(self):
self.conn = pymssql.connect(
host = settings['MSSQL_HOST'],
user = settings['MSSQL_USER'],
password = settings['MSSQL_PASSWD'],
database = settings['MSSQL_DBNAME']
)
self.cursor = self.conn.cursor()
self.cursor.execute('truncate table lgjob;')
self.conn.commit()

def process_item(self, item, spider):
try:
self.cursor.execute(
"""INSERT INTO lgjob( companyfullname , positionname, salary, workyear, education
, city,district, financestage, industryfield, firsttype, positionlables)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)""",
(
item['companyfullname'],
item['positionname'],
item['salary'],
item['workyear'],
item['education'],
item['city'],
item['district'],
item['financestage'],
item['industryfield'],
item['firsttype'],
item['positionlables']
)
)
self.conn.commit()
except pymssql.Error as e:
print(e)
return item


【settings.py】配置参数(如数据库连接信息)

# -*- coding: utf-8 -*-

# Scrapy settings for lgjob project
#
# For simplicity, this file contains only settings considered important or
# commonly used. You can find more settings consulting the documentation:
#
# http://doc.scrapy.org/en/latest/topics/settings.html
# http://scrapy.readthedocs.org/en/latest/topics/downloader-middleware.html
# http://scrapy.readthedocs.org/en/latest/topics/spider-middleware.html

BOT_NAME = 'lgjob'

SPIDER_MODULES = ['lgjob.spiders']
NEWSPIDER_MODULE = 'lgjob.spiders'

MSSQL_HOST = 'HZC'
MSSQL_USER = 'kk'
MSSQL_PASSWD = 'kk'
MSSQL_DBNAME = 'Myspider'

ITEM_PIPELINES = {
'lgjob.pipelines.LgjobPipeline': 300,
}

【main.py】

# -*- coding: utf-8 -*-
# python 3.5
# file path ../lgjob/lgjob/spiders/main.py
# perform: scrapy crawl lgjob

import json
import scrapy
from lgjob.items import LgjobItem
from bs4 import BeautifulSoup

class MainLgjob(scrapy.Spider):
name = 'lgjob'
domain = ['lagou.com']
start_url = ['https://www.lagou.com/zhaopin/']

curpage = 1
totalPageCount = 6
keyword = "DBA"
cururl = "https://www.lagou.com/zhaopin/%s/%s/?filterOption=3"%(keyword,curpage)

def start_requests(self):
return [scrapy.http.FormRequest(self.cururl,callback=self.parse)]

def parse(self, response):
soup = BeautifulSoup(response.body,'html.parser',from_encoding='utf-8')
body_ul = soup.find_all("li" ,class_="con_list_item default_list")
for li in body_ul:
item = LgjobItem()
arg1 = li.find("div",class_="position").find("div",class_="p_top").find("em").get_text(strip=True)
arg2 = li.find("div",class_="position").find("div",class_="li_b_l").get_text(" / ",strip=True)
arg3 = li.find("div",class_="company").find("div",class_="industry").get_text(strip=True)
arg4 = li.find("div",class_="list_item_bot").find("div",class_="li_b_r").get_text(strip=True)
item['companyfullname'] = li.find("div",class_="company").find("div",class_="company_name").find("a").get_text(strip=True)
item['positionname'] = li.find("div",class_="position").find("div",class_="p_top").find("h3").get_text(strip=True)
item['salary'] = ((arg2 + "/").split('/')[0]).strip()
item['workyear'] = ((arg2 + "/").split('/')[1]).strip()
item['education'] = ((arg2 + "/").split('/')[2]).strip()
item['city'] = (arg1+'·'+arg1).split('·')[0]
item['district'] = (arg1+'·'+arg1).split('·')[1]
item['industryfield'] = ((arg3 + "/").split('/')[0]).strip()
item['financestage'] = ((arg3 + "/").split('/')[1]).strip()
item['positionlables'] = arg4.strip('“').strip('”')
item['firsttype'] = li.find("div",class_="list_item_bot").find("div",class_="li_b_l").get_text(",",strip=True)
yield item

if self.curpage < self.totalPageCount:
self.curpage += 1
self.cururl = "https://www.lagou.com/zhaopin/%s/%s/?filterOption=3"%(self.keyword,self.curpage)
yield scrapy.http.FormRequest(self.cururl,callback=self.parse)


main.py 这个脚本有几个缺点,还没完善:

1. 总的页码需要手动定义(可以参考第一张图中的总记录计算总页码)

2. 其他筛选条件没有(只有搜索的岗位名称,城市则是在拉钩上设置的默认地方)

3. 访问到第6页时,则出现 302 重定向,爬虫终止结束了!


现在解决 1和3 的问题。


1. 总页数可以从web显示的页码那里获取(或者底部),如下图

Python scrapy 爬取拉勾网招聘信息_ci_03

Python scrapy 爬取拉勾网招聘信息_html_04


定位到标签中,获取总页码:

page_num= soup.find("div" ,class_="page-number").find("span" ,class_="span totalNum").get_text(strip=True)

self.totalPageCount = int(page_num)


 302 的问题,请求时添加 cookie,使其可以访问更多页。

登录之后找到cookie,cookie内有很多 “属性=值,属性=值…”,稍后都改为“键: 值,键: 值…” 。同样也可以看到其他 headers 信息。

Python scrapy 爬取拉勾网招聘信息_html_05


【settings.py】此时的配置文件内容改为如下(cookie都改为“key-value”格式,顺便也添加了header)

# -*- coding: utf-8 -*-

# Scrapy settings for lgjob project

BOT_NAME = 'lgjob'

SPIDER_MODULES = ['lgjob.spiders']
NEWSPIDER_MODULE = 'lgjob.spiders'

MSSQL_HOST = 'HZC'
MSSQL_USER = 'kk'
MSSQL_PASSWD = 'kk'
MSSQL_DBNAME = 'Myspider'

ITEM_PIPELINES = {
'lgjob.pipelines.LgjobPipeline': 300,
}

USER_AGENT = "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.78 Safari/537.36"


#以下为使用 cookie 时添加. 不使用的话在文件 MainLgjob 注释
ROBOTSTXT_OBEY = False #不遵守Robot协议
DOWNLOAD_DELAY = 3 #延迟
COOKIES_ENABLED = True #启用 cookie

HEADERS = {
'Connection': 'keep-alive',
'User-Agent': 'Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.78 Safari/537.36'
}
META = {
'dont_redirect': True,
'handle_httpstatus_list': [301, 302]
}
COOKIES = {
'user_trace_token': 'xxxxxxxxxxxxxxxxxxxxxxxxxx',
'LGUID': 'xxxxxxxxxxxxxxxxxxxxxxxxxx',
'sensorsdata2015jssdkcross': 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
'_qddaz': 'QD.vr42sh.xxxxxxxxxxxxx.xxxxxxxxxxxxx',
'JSESSIONID': 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
'_putrc': 'xxxxxxxxxxxxxxxxxxxxxxxxxx',
'login': 'true',
'unick': 'xxxxxxxxxxxxxxxxxxxxxxxxxx',
'showExpriedIndex': '1',
'showExpriedCompanyHome': '1',
'showExpriedMyPublish': '1',
'hasDeliver': '49',
'SEARCH_ID': 'xxxxxxxxxxxxxxxxxxxxxxxxxx',
'index_location_city': 'xxxxxxxxxxxxx',
'Hm_lvt_4233e74dff0ae5bd0a3d81c6ccf756e6': 'xxxxxxxxxxxxx',
'Hm_lpvt_4233e74dff0ae5bd0a3d81c6ccf756e6': 'xxxxxxxxxxxxx',
'_ga': 'GA1.2.437277179',
'LGRID': 'xxxxxxxxxxxxx'
}

【main.py】请求信息 scrapy.http.FormRequest 把 cookie 和 header 也加上去

# -*- coding: utf-8 -*-
# python 3.5
# file path ../lgjob/lgjob/spiders/main.py
# perform: scrapy crawl lgjob

import json
import scrapy
from lgjob.items import LgjobItem
from bs4 import BeautifulSoup
from scrapy.conf import settings

class MainLgjob(scrapy.Spider):
name = 'lgjob'
domain = ['.lagou.com']
start_url = ['https://www.lagou.com/zhaopin/']

#不使用cookie,注释 Request的 ,headers=self.headers, cookies=self.cookies, meta=self.meta
meta = settings['META']
cookies = settings['COOKIES']
headers = settings['HEADERS']

curpage = 1
totalPageCount = 1
keyword = u"DBA"
cururl = "https://www.lagou.com/zhaopin/%s/%s/?filterOption=3"%(keyword,curpage)

def start_requests(self):
return [scrapy.http.FormRequest(self.cururl,callback=self.parse,headers=self.headers, cookies=self.cookies, meta=self.meta)]

def parse(self, response):
soup = BeautifulSoup(response.body,'html.parser',from_encoding='utf-8')
body_ul = soup.find_all("li" ,class_="con_list_item default_list")

#每次获取总页码
page_num= soup.find("div" ,class_="page-number").find("span" ,class_="span totalNum").get_text(strip=True)
self.totalPageCount = int(page_num)
for li in body_ul:
item = LgjobItem()
arg1 = li.find("div",class_="position").find("div",class_="p_top").find("em").get_text(strip=True)
arg2 = li.find("div",class_="position").find("div",class_="li_b_l").get_text(" / ",strip=True)
arg3 = li.find("div",class_="company").find("div",class_="industry").get_text(strip=True)
arg4 = li.find("div",class_="list_item_bot").find("div",class_="li_b_r").get_text(strip=True)
item['companyfullname'] = li.find("div",class_="company").find("div",class_="company_name").find("a").get_text(strip=True)
item['positionname'] = li.find("div",class_="position").find("div",class_="p_top").find("h3").get_text(strip=True)
item['salary'] = ((arg2 + "/").split('/')[0]).strip()
item['workyear'] = ((arg2 + "/").split('/')[1]).strip()
item['education'] = ((arg2 + "/").split('/')[2]).strip()
item['city'] = (arg1+'·'+arg1).split('·')[0]
item['district'] = (arg1+'·'+arg1).split('·')[1]
item['industryfield'] = ((arg3 + "/").split('/')[0]).strip()
item['financestage'] = ((arg3 + "/").split('/')[1]).strip()
item['positionlables'] = arg4.strip('“').strip('”')
item['firsttype'] = li.find("div",class_="list_item_bot").find("div",class_="li_b_l").get_text(",",strip=True)
yield item

if self.curpage < self.totalPageCount:
self.curpage += 1
self.cururl = "https://www.lagou.com/zhaopin/%s/%s/?filterOption=3"%(self.keyword,self.curpage)
yield scrapy.http.FormRequest(self.cururl,callback=self.parse,headers=self.headers, cookies=self.cookies, meta=self.meta)

执行爬取命令: scrapy startproject lgjob

爬取过程中命令行页面会显示每个属性值,本人电脑执行大约每5秒完成拉钩一页15个公司的数据处理。

最终结果如下:

Python scrapy 爬取拉勾网招聘信息_ide_06


参考:

​Beautiful Soup 4.2.0 文档​

​Scrapy 0.24 文档​

​​【python爬虫02】使用Scrapy框架爬取拉勾网招聘信息​​

​​【图文详解】scrapy爬虫与动态页面——爬取拉勾网职位信息(1)​​

​​Python爬虫系列之----Scrapy(八)爬取豆瓣读书某个tag下的所有书籍并保存到Mysql数据库中去​​

​​Scrapy中使用cookie免于验证登录和模拟登录​​