Python scrapy 爬取拉勾網招聘資訊
週末折騰了好久,終於成功把拉鉤網的招聘資訊爬取下來了。現在總結一下!
環境: 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 格式的,如下圖:
這種方法是比較完整的,每個公司的招聘資訊都很完整。拉鉤網預設每頁顯示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 再右鍵某個職位名稱:
每頁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顯示的頁碼那裡獲取(或者底部),如下圖
定位到標籤中,獲取總頁碼:
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 資訊。
【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個公司的資料處理。
最終結果如下:
參考: