Python爬蟲實(shí)例——scrapy框架爬取拉勾網(wǎng)招聘信息
本文實(shí)例為爬取拉勾網(wǎng)上的python相關(guān)的職位信息, 這些信息在職位詳情頁(yè)上, 如職位名, 薪資, 公司名等等.
分析思路
分析查詢結(jié)果頁(yè)
在拉勾網(wǎng)搜索框中搜索'python'關(guān)鍵字, 在瀏覽器地址欄可以看到搜索結(jié)果頁(yè)的url為: 'https://www.lagou.com/jobs/list_python?labelWords=&fromSearch=true&suginput=', 嘗試將?后的參數(shù)刪除, 發(fā)現(xiàn)訪問(wèn)結(jié)果相同.
打開Chrome網(wǎng)頁(yè)調(diào)試工具(F12), 分析每條搜索結(jié)果(即每個(gè)職位)在html中所處的元素定位, 發(fā)現(xiàn)每條結(jié)果都在<ul class="item_con_list">下的li標(biāo)簽中.
因?yàn)槲覀冃枰總€(gè)職位的具體信息, 因此需要獲取到每條搜索結(jié)果的詳情url, 即點(diǎn)擊搜索結(jié)果后進(jìn)入的詳情頁(yè)的url.
繼續(xù)查看li標(biāo)簽中的元素, 找到想要的詳情url, 找到后的url為: href=https://www.lagou.com/jobs/6945237.html?show=b6e8e778fcae4c2aa2111ba58f9ebfa0
查看其它搜索結(jié)果的詳情url, 發(fā)現(xiàn)其格式都為: rel="external nofollow"
對(duì)于第一個(gè)ID, 每條結(jié)果的id都不一樣, 猜想其為標(biāo)記每個(gè)職位的唯一id, 對(duì)于show_id, 每條結(jié)果的id都是一樣的, 嘗試刪除show參數(shù), 發(fā)現(xiàn)一樣可以訪問(wèn)到具體結(jié)果詳情頁(yè)
那么我們直接通過(guò)xpath提取到每個(gè)職位的第一個(gè)ID即可, 但是調(diào)試工具的elements標(biāo)簽下的html是最終網(wǎng)頁(yè)展示的html, 并不一定就是我們?cè)L問(wèn) https://www.lagou.com/jobs/list_python 返回的response的html, 因此點(diǎn)到Network標(biāo)簽, 重新刷新一下頁(yè)面, 找到 https://www.lagou.com/jobs/list_python 對(duì)應(yīng)的請(qǐng)求, 查看其對(duì)應(yīng)的response, 搜索 'position_link'(即前面我們?cè)趀lements中找到的每條搜索結(jié)果的詳情url), 發(fā)現(xiàn)確實(shí)返回了一個(gè)網(wǎng)址, 但是其重要的兩個(gè)ID并不是直接放回的, 而是通過(guò)js生成的, 說(shuō)明我們想要的具體數(shù)據(jù)并不是這個(gè)這個(gè)請(qǐng)求返回的.
那么我們就需要找到具體是那個(gè)請(qǐng)求會(huì)返回搜索結(jié)果的信息, 一般這種情況首先考慮是不是通過(guò)ajax獲取的數(shù)據(jù), 篩選類型為XHR(ajax)的請(qǐng)求, 可以逐個(gè)點(diǎn)開查看response, 發(fā)現(xiàn) positionAjax.json 返回的數(shù)據(jù)中就存在我們想要的每條搜索結(jié)果的信息. 說(shuō)明確實(shí)是通過(guò)ajax獲取的數(shù)據(jù), 其實(shí)點(diǎn)擊下一頁(yè), 我們也可以發(fā)現(xiàn)地址欄url地址并沒有發(fā)生變化, 只是局部刷新了搜索結(jié)果的數(shù)據(jù), 也說(shuō)明了搜索結(jié)果是通過(guò)ajax返回的.
分析上面ajax的response, 查看其中是否有我們想要的職位ID, 在preview中搜索之前在elements中找到的某個(gè)職位的url的兩個(gè)ID, 確實(shí)兩個(gè)ID都存在response中, 分析發(fā)現(xiàn)第一個(gè)ID即為positionId, 第二個(gè)即為showId, 我們還可以發(fā)現(xiàn)response中返回了當(dāng)前的頁(yè)碼數(shù)pageNo
因此我們只需要訪問(wèn)上面ajax對(duì)應(yīng)的url: https://www.lagou.com/jobs/positionAjax.json?needAddtionalResult=false 就可以拿到我們想要的ID, 然后填入詳情url模板: https://www.lagou.com/jobs/{position_id}.html?show={show_id}中即可訪問(wèn)詳情頁(yè)了.
但是當(dāng)我們直接訪問(wèn) https://www.lagou.com/jobs/positionAjax.json?needAddtionalResult=false 時(shí) ,返回的結(jié)果卻是: {"status":false,"msg":"您操作太頻繁,請(qǐng)稍后再訪問(wèn)","clientIp":"139.226.66.44","state":2402}
經(jīng)過(guò)百度查詢后發(fā)現(xiàn)原來(lái)直接訪問(wèn)上述地址是不行的, 這也是拉鉤的一個(gè)反爬策略, 需要我們帶上之前訪問(wèn)查詢結(jié)果頁(yè)(https://www.lagou.com/jobs/list_python?)的cookie才行, 因?yàn)槲覀冞@里使用的是scrapy框架, 該框架是能夠自帶上次請(qǐng)求的cookie來(lái)訪問(wèn)下一個(gè)請(qǐng)求的, 所以我們這里不需要手動(dòng)去添加cookie信息, 只需要首先訪問(wèn)一下查詢結(jié)果頁(yè)就可以了. 即start_url = https://www.lagou.com/jobs/list_python
此外發(fā)現(xiàn)這個(gè)ajax請(qǐng)求是通過(guò)POST方式發(fā)送的, 因此還需要分析它提交的form數(shù)據(jù), 在第一頁(yè)中有三條數(shù)據(jù)信息, first為true, pn為1 kd為python , 第二頁(yè)中first為false, pn為2, kd同樣為python, 且多了一個(gè)sid
分析這四個(gè)參數(shù), 第一個(gè)first為表示是否是第一頁(yè), 第二個(gè)pn為表示當(dāng)前頁(yè)碼數(shù), 第三個(gè)kd為表示搜索的關(guān)鍵字, 第四個(gè)sid經(jīng)過(guò)和上面showId對(duì)比發(fā)現(xiàn)其值就為showId
分析職位詳情頁(yè)
前面分析完后就可以拼接出職位詳情頁(yè)url了, 點(diǎn)開詳情頁(yè), 同樣的思路分析我們想要的數(shù)據(jù)是不是就在詳情頁(yè)的url中, 這里想要職位名稱, 工資, 地點(diǎn), 經(jīng)驗(yàn), 關(guān)鍵字, 公司信息等
在network中查找對(duì)應(yīng)的response, 發(fā)現(xiàn)數(shù)據(jù)確實(shí)就存在response中, 因此直接通過(guò)xpath就可以提取想要的數(shù)據(jù)了
編寫爬蟲代碼
具體代碼在github:
這里只放出關(guān)鍵代碼
創(chuàng)建scrapy項(xiàng)目
scrapy startproject LaGou
創(chuàng)建爬蟲
scrapy genspider lagou www.lagou.com
編寫items.py, 設(shè)置要想爬取的字段
# -*- 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): # 設(shè)置頭信息, 若不設(shè)置的話, 在請(qǐng)求第二頁(yè)時(shí)即被拉勾網(wǎng)認(rèn)為是爬蟲而不能爬取數(shù)據(jù) 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): """ 解析起始頁(yè) """ # response為GET請(qǐng)求的起始頁(yè), 自動(dòng)獲取cookie # 提交POST帶上前面返回的cookies, 訪問(wèn)數(shù)據(jù)結(jié)果第一頁(yè) 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): """ 解析結(jié)果列表頁(yè)的json數(shù)據(jù) """ # 獲取返回的json,轉(zhuǎn)為字典 res_dict = json.loads(response.text) # 判斷返回是否成功 if not res_dict.get('success'): print(res_dict.get('msg', '返回異常')) else: # 獲取當(dāng)前頁(yè)數(shù) page_num = res_dict['content']['pageNo'] print('正在爬取第{}頁(yè)'.format(page_num)) # 獲取sid if not self.sid: self.sid = res_dict['content']['showId'] # 獲取響應(yīng)中的職位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) # 請(qǐng)求職位詳情頁(yè) yield scrapy.Request( item['job_url'], callback=self.parse_detail, headers=self.headers, meta={'item': item} ) # 獲取下一頁(yè) if page_num < 30: # time.sleep(2) yield scrapy.FormRequest( 'https://www.lagou.com/jobs/positionAjax.json?needAddtionalResult=false', callback=self.parse_list, formdata={"first": "false", "pn": str(page_num+1), "kd": "python", "sid": self.sid }, headers=self.headers ) def parse_detail(self, response): """ 解析職位詳情頁(yè) """ # 接收item item = response.meta['item'] # 解析數(shù)據(jù) # 獲取職位頭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, 用來(lái)每次發(fā)送請(qǐng)求前, 隨機(jī)設(shè)置user-agent, 這里使用了第三方庫(kù) fake_useragent, 能夠隨機(jī)提供user-agent, 使用前先安裝: pip install fake_useragent
from fake_useragent import UserAgent import random class RandomUserAgentDM: """ 隨機(jī)獲取userAgent """ def __init__(self): self.user_agent = UserAgent() def process_request(self, request, spider): request.headers['User-Agent'] = self.user_agent.random
編寫pipelines.py, 將數(shù)據(jù)存為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
# 設(shè)置日志顯示 LOG_LEVEL = 'WARNING' # 設(shè)置ROBOTSTXT協(xié)議, 若為true則不能爬取數(shù)據(jù) ROBOTSTXT_OBEY = False # 設(shè)置下載器延遲, 反爬蟲的一種策略 DOWNLOAD_DELAY = 0.25 # 開啟DOWNLOADER_MIDDLEWARES DOWNLOADER_MIDDLEWARES = { # 'LaGou.middlewares.LagouDownloaderMiddleware': 543, 'LaGou.middlewares.RandomUserAgentDM' :100, } # 開啟ITEM_PIPELINES ITEM_PIPELINES = { 'LaGou.pipelines.LagouPipeline': 300, }
啟動(dòng)爬蟲
scrapy crawl lagou
發(fā)現(xiàn)依然只能5 6頁(yè), 說(shuō)明拉勾網(wǎng)的反爬確實(shí)做得比較好, 還可以繼續(xù)通過(guò)使用代理來(lái)進(jìn)行反反爬, 這里就不再演示了,
查看爬取結(jié)果
以上就是Python爬蟲實(shí)例——scrapy框架爬取拉勾網(wǎng)招聘信息的詳細(xì)內(nèi)容,更多關(guān)于Python爬蟲爬取招聘信息的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
python中cv2.projectPoints的用法小結(jié)
這篇文章主要介紹了python中cv2.projectPoints的用法,本文通過(guò)示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2023-12-12python中is與雙等于號(hào)“==”的區(qū)別示例詳解
Python中有很多運(yùn)算符,下面這篇文章主要給大家介紹了關(guān)于python中is與雙等于號(hào)“==”區(qū)別的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。2017-11-11python按照行來(lái)讀取txt文件全部?jī)?nèi)容(去除空行處理掉\t,\n后以列表方式返回)
這篇文章主要介紹了python按照行來(lái)讀取txt文件全部?jī)?nèi)容 ,去除空行,處理掉\t,\n后,以列表方式返回,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2023-06-06python通過(guò)pil模塊獲得圖片exif信息的方法
這篇文章主要介紹了python通過(guò)pil模塊獲得圖片exif信息的方法,實(shí)例分析了Python中pil模塊的使用技巧,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2015-03-03Python爬蟲使用瀏覽器cookies:browsercookie過(guò)程解析
這篇文章主要介紹了Python爬蟲使用瀏覽器cookies:browsercookie,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-10-10在Django中實(shí)現(xiàn)定時(shí)任務(wù)的多種方法
在 Django 項(xiàng)目中實(shí)現(xiàn)定時(shí)任務(wù)可以幫助自動(dòng)化執(zhí)行一些后臺(tái)任務(wù),如數(shù)據(jù)清理、定期報(bào)告生成等,以下是幾種常見的實(shí)現(xiàn)方式,每種方法都有其獨(dú)特的優(yōu)勢(shì)和適用場(chǎng)景,感興趣的小伙伴跟著小編一起來(lái)看看吧2024-08-08Python中dilb和face_recognition第三方包安裝失敗的解決
本文主要介紹了Python中dilb和face_recognition第三方包安裝失敗的解決,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-02-02