1. 程式人生 > >Python scrapy 爬取拉勾網招聘資訊

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個公司的資料處理。

最終結果如下:


參考: