Python爬蟲例項——scrapy框架爬取拉勾網招聘資訊
本文例項為爬取拉勾網上的python相關的職位資訊,這些資訊在職位詳情頁上,如職位名,薪資,公司名等等.
分析思路
分析查詢結果頁
在拉勾網搜尋框中搜索'python'關鍵字,在瀏覽器位址列可以看到搜尋結果頁的url為: 'https://www.lagou.com/jobs/list_python?labelWords=&fromSearch=true&suginput=',嘗試將?後的引數刪除,發現訪問結果相同.
開啟Chrome網頁除錯工具(F12),分析每條搜尋結果(即每個職位)在html中所處的元素定位,發現每條結果都在<ul class="item_con_list">下的li標籤中.
因為我們需要每個職位的具體資訊,因此需要獲取到每條搜尋結果的詳情url,即點選搜尋結果後進入的詳情頁的url.
繼續檢視li標籤中的元素,找到想要的詳情url,找到後的url為: href=https://www.lagou.com/jobs/6945237.html?show=b6e8e778fcae4c2aa2111ba58f9ebfa0
檢視其它搜尋結果的詳情url,發現其格式都為: href="https://www.lagou.com/jobs/{某個id}.html?show={show_id}" rel="external nofollow"
對於第一個ID,每條結果的id都不一樣,猜想其為標記每個職位的唯一id,對於show_id,每條結果的id都是一樣的,嘗試刪除show引數,發現一樣可以訪問到具體結果詳情頁
那麼我們直接通過xpath提取到每個職位的第一個ID即可,但是除錯工具的elements標籤下的html是最終網頁展示的html,並不一定就是我們訪問 https://www.lagou.com/jobs/list_python 返回的response的html,因此點到Network標籤,重新重新整理一下頁面,找到 https://www.lagou.com/jobs/list_python 對應的請求,檢視其對應的response,搜尋 'position_link'(即前面我們在elements中找到的每條搜尋結果的詳情url),發現確實返回了一個網址,但是其重要的兩個ID並不是直接放回的,而是通過js生成的,說明我們想要的具體資料並不是這個這個請求返回的.
那麼我們就需要找到具體是那個請求會返回搜尋結果的資訊,一般這種情況首先考慮是不是通過ajax獲取的資料,篩選型別為XHR(ajax)的請求,可以逐個點開檢視response,發現 positionAjax.json 返回的資料中就存在我們想要的每條搜尋結果的資訊. 說明確實是通過ajax獲取的資料,其實點選下一頁,我們也可以發現位址列url地址並沒有發生變化,只是區域性重新整理了搜尋結果的資料,也說明了搜尋結果是通過ajax返回的.
分析上面ajax的response,檢視其中是否有我們想要的職位ID,在preview中搜索之前在elements中找到的某個職位的url的兩個ID,確實兩個ID都存在response中,分析發現第一個ID即為positionId,第二個即為showId,我們還可以發現response中返回了當前的頁碼數pageNo
因此我們只需要訪問上面ajax對應的url: https://www.lagou.com/jobs/positionAjax.json?needAddtionalResult=false 就可以拿到我們想要的ID,然後填入詳情url模板: https://www.lagou.com/jobs/{position_id}.html?show={show_id}中即可訪問詳情頁了.
但是當我們直接訪問 https://www.lagou.com/jobs/positionAjax.json?needAddtionalResult=false 時,返回的結果卻是: {"status":false,"msg":"您操作太頻繁,請稍後再訪問","clientIp":"139.226.66.44","state":2402}
經過百度查詢後發現原來直接訪問上述地址是不行的,這也是拉鉤的一個反爬策略,需要我們帶上之前訪問查詢結果頁(https://www.lagou.com/jobs/list_python?)的cookie才行,因為我們這裡使用的是scrapy框架,該框架是能夠自帶上次請求的cookie來訪問下一個請求的,所以我們這裡不需要手動去新增cookie資訊,只需要首先訪問一下查詢結果頁就可以了. 即start_url = https://www.lagou.com/jobs/list_python
此外發現這個ajax請求是通過POST方式傳送的,因此還需要分析它提交的form資料,在第一頁中有三條資料資訊,first為true,pn為1 kd為python,第二頁中first為false,pn為2,kd同樣為python,且多了一個sid
分析這四個引數,第一個first為表示是否是第一頁,第二個pn為表示當前頁碼數,第三個kd為表示搜尋的關鍵字,第四個sid經過和上面showId對比發現其值就為showId
分析職位詳情頁
前面分析完後就可以拼接出職位詳情頁url了,點開詳情頁,同樣的思路分析我們想要的資料是不是就在詳情頁的url中,這裡想要職位名稱,工資,地點,經驗,關鍵字,公司資訊等
在network中查詢對應的response,發現數據確實就存在response中,因此直接通過xpath就可以提取想要的資料了
編寫爬蟲程式碼
具體程式碼在github:
這裡只放出關鍵程式碼
建立scrapy專案
scrapy startproject LaGou
建立爬蟲
scrapy genspider lagou www.lagou.com
編寫items.py,設定要想爬取的欄位
# -*- coding: utf-8 -*- # Define here the models for your scraped items # # See documentation in: # https://docs.scrapy.org/en/latest/topics/items.html import scrapy class LagouItem(scrapy.Item): # define the fields for your item here like: job_url = scrapy.Field() job_name = scrapy.Field() salary = scrapy.Field() city = scrapy.Field() area = scrapy.Field() experience = scrapy.Field() education = scrapy.Field() labels = scrapy.Field() publish_date = scrapy.Field() company = scrapy.Field() company_feature = scrapy.Field() company_public = scrapy.Field() company_size= scrapy.Field()
編寫爬蟲程式碼 lagou.py
# -*- coding: utf-8 -*- import scrapy from LaGou.items import LagouItem import json from pprint import pprint import time class LagouSpider(scrapy.Spider): name = 'lagou' allowed_domains = ['www.lagou.com'] start_urls = ['https://www.lagou.com/jobs/list_python?'] def __init__(self): # 設定頭資訊,若不設定的話,在請求第二頁時即被拉勾網認為是爬蟲而不能爬取資料 self.headers = { "Accept": "application/json,text/javascript,*/*; q=0.01","Connection": "keep-alive","Host": "www.lagou.com","Referer": 'https://www.lagou.com/jobs/list_Python?',"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8","referer": "https://www.lagou.com/jobs/list_python?" } self.sid = '' self.job_url_temp = 'https://www.lagou.com/jobs/{}.html?show={}' # 清空檔案 with open('jobs.json','w') as f: f.truncate() def parse(self,response): """ 解析起始頁 """ # response為GET請求的起始頁,自動獲取cookie # 提交POST帶上前面返回的cookies,訪問資料結果第一頁 yield scrapy.FormRequest( 'https://www.lagou.com/jobs/positionAjax.json?needAddtionalResult=false',callback=self.parse_list,formdata={"first": "false","pn": "1","kd": "python",},headers=self.headers ) def parse_list(self,response): """ 解析結果列表頁的json資料 """ # 獲取返回的json,轉為字典 res_dict = json.loads(response.text) # 判斷返回是否成功 if not res_dict.get('success'): print(res_dict.get('msg','返回異常')) else: # 獲取當前頁數 page_num = res_dict['content']['pageNo'] print('正在爬取第{}頁'.format(page_num)) # 獲取sid if not self.sid: self.sid = res_dict['content']['showId'] # 獲取響應中的職位url字典 part_url_dict = res_dict['content']['hrInfoMap'] # 遍歷職位字典 for key in part_url_dict: # 初始化儲存職位的item item = LagouItem() # 拼接完整職位url item['job_url'] = self.job_url_temp.format(key,self.sid) # 請求職位詳情頁 yield scrapy.Request( item['job_url'],callback=self.parse_detail,headers=self.headers,meta={'item': item} ) # 獲取下一頁 if page_num < 30: # time.sleep(2) yield scrapy.FormRequest( 'https://www.lagou.com/jobs/positionAjax.json?needAddtionalResult=false',"pn": str(page_num+1),"sid": self.sid },headers=self.headers ) def parse_detail(self,response): """ 解析職位詳情頁 """ # 接收item item = response.meta['item'] # 解析資料 # 獲取職位頭div job_div = response.xpath('//div[@class="position-content-l"]') if job_div: item['job_name'] = job_div.xpath('./div/h1/text()').extract_first() item['salary'] = job_div.xpath('./dd/h3/span[1]/text()').extract_first().strip() item['city'] = job_div.xpath('./dd/h3/span[2]/text()').extract_first().strip('/').strip() item['area'] = response.xpath('//div[@class="work_addr"]/a[2]/text()').extract_first() item['experience'] = job_div.xpath('./dd/h3/span[3]/text()').extract_first().strip('/').strip() item['education'] = job_div.xpath('./dd/h3/span[4]/text()').extract_first().strip('/').strip() item['labels'] = response.xpath('//ul[@class="position-label clearfix"]/li/text()').extract() item['publish_date'] = response.xpath('//p[@class="publish_time"]/text()').extract_first() item['publish_date'] = item['publish_date'].split('&')[0] # 獲取公司dl company_div = response.xpath('//dl[@class="job_company"]') item['company'] = company_div.xpath('./dt/a/img/@alt').extract_first() item['company_feature'] = company_div.xpath('./dd//li[1]/h4[@class="c_feature_name"]/text()').extract_first() item['company_feature'] = item['company_feature'].split(',') item['company_public'] = company_div.xpath('./dd//li[2]/h4[@class="c_feature_name"]/text()').extract_first() item['company_size'] = company_div.xpath('./dd//li[4]/h4[@class="c_feature_name"]/text()').extract_first() yield item
編寫middlewares.py,自定義downloadermiddleware,用來每次傳送請求前,隨機設定user-agent,這裡使用了第三方庫 fake_useragent,能夠隨機提供user-agent,使用前先安裝: pip install fake_useragent
from fake_useragent import UserAgent import random class RandomUserAgentDM: """ 隨機獲取userAgent """ def __init__(self): self.user_agent = UserAgent() def process_request(self,request,spider): request.headers['User-Agent'] = self.user_agent.random
編寫pipelines.py,將資料存為json檔案
import json class LagouPipeline: def process_item(self,item,spider): with open('jobs.json','a',encoding='utf-8') as f: item_json = json.dumps(dict(item),ensure_ascii=False,indent=2) f.write(item_json) f.write('\n')
編寫settings.py
# 設定日誌顯示 LOG_LEVEL = 'WARNING' # 設定ROBOTSTXT協議,若為true則不能爬取資料 ROBOTSTXT_OBEY = False # 設定下載器延遲,反爬蟲的一種策略 DOWNLOAD_DELAY = 0.25 # 開啟DOWNLOADER_MIDDLEWARES DOWNLOADER_MIDDLEWARES = { # 'LaGou.middlewares.LagouDownloaderMiddleware': 543,'LaGou.middlewares.RandomUserAgentDM' :100,} # 開啟ITEM_PIPELINES ITEM_PIPELINES = { 'LaGou.pipelines.LagouPipeline': 300,}
啟動爬蟲
scrapy crawl lagou
發現依然只能5 6頁,說明拉勾網的反爬確實做得比較好,還可以繼續通過使用代理來進行反反爬,這裡就不再演示了,
檢視爬取結果
以上就是Python爬蟲例項——scrapy框架爬取拉勾網招聘資訊的詳細內容,更多關於Python爬蟲爬取招聘資訊的資料請關注我們其它相關文章!