【Scrapy】CrawlSpider 單頁面Ajax爬取
專案目標
爬取拉勾網職位列表基本資訊+職位描述
專案思考
拉勾網的招聘崗位列表,這是Ajax非同步載入的。
我想把崗位列表所顯示的資訊爬取下來,同時還需要崗位的工作詳情。
爬取流程就是一開始就不斷獲取職位列表的json,然後從json中提取對應的職位詳情頁,再進去爬取其職位描述。
使用Scrapy的scrapy.Spider基礎爬蟲模板很簡單就可以實現,直接過載編寫parse方法,再加上個回撥方法就可以。
但如何使用CrawlSpider做到類似的功能?
對於獲取json的網址,rules規則沒什麼用,而CrawlSpider中的parse是不能被過載的。
-
第一步,重寫start_request():
crawlspider繼承基類是spider,所以它的開始入口也是start_request(),然後預設回撥parse。注意回撥parse這個不能改。
-
第二步,重寫parse_start_url()
start_request()->parse()->_parse_response()->parse_start_url()
如果設定了callback就會呼叫parse_start_url()方法,rules中的回撥。
因為rules在本次專案中沒有作用,所以我們需要過載parse_start_url()作為我們的回撥方法。
在parse_start_url()中,需要獲取職位的詳情頁,發起request,設定回撥方法。
不斷髮起下一頁的職位列表請求。 -
第三步,編寫解析職位描述的detail_parse()
注意的時,這裡最後在parse_start_url()中使用response.meta傳遞item到detail_parse()來進行資料儲存。
因為如果在parse_start_url()就把職位列表的資訊儲存下來的話,因為Scrapy程式排程的關係,在插入資料庫的時候,item中的資料不一定同步,插入資料庫會報一些錯。
-
注意request要帶上有cookie的header,不然會被重定向到login頁面
專案程式碼
# -*- coding: utf-8 -*- import json import random import time from datetime import datetime import scrapy from scrapy import FormRequest from scrapy.spiders import CrawlSpider, Rule from utils.common import get_md5 from items import LagouJobItemLoader, LagouItem class LagouSpider(CrawlSpider): name = 'lagou' allowed_domains = ['www.lagou.com'] start_urls = ['https://www.lagou.com/jobs/positionAjax.json?'] headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36', 'Content-Type': 'application/x-www-form-urlencoded; set=UTF-8', 'Cookie': 'JSESSIONID=ABAAABAAAGFABEF2A9F526EEAF8A4D5979C9C91C470D916; user_trace_token=20181108222228-77038827-4d01-4f91-8b3a-55a0cccaf9d6; Hm_lvt_4233e74dff0ae5bd0a3d81c6ccf756e6=1541686949; _ga=GA1.2.1567223367.1541686949; _gid=GA1.2.489493353.1541686949; LGUID=20181108222230-b9c05f9e-e361-11e8-9314-525400f775ce; TG-TRACK-CODE=search_code; Hm_lpvt_4233e74dff0ae5bd0a3d81c6ccf756e6=1541777035; LGRID=20181109232414-8446b9ff-e433-11e8-94aa-525400f775ce; SEARCH_ID=e89119713fd54563ad1b598a8c7e631a', 'Referer': 'https://www.lagou.com/jobs/list_Python?labelWords=&fromSearch=true&suginput=1', 'Host': 'www.lagou.com', 'Origin': 'https: // www.lagou.com', 'X-Anit-Forge-Code': 0, 'X-Anit-Forge-Token': None, 'X-Requested-With': 'XMLHttpRequest' } page = 1 def start_requests(self): for url in self.start_urls: yield FormRequest(url, headers=self.headers, formdata={ 'first': 'true', 'pn': str(self.page), 'kd': 'Python', 'city': '深圳' }, callback=self.parse, dont_filter=True ) def parse_start_url(self, response): data = json.loads(response.body.decode('utf-8')) result = data['content']['positionResult']['result'] totalCount = data['content']['positionResult']['totalCount'] resultSize = data['content']['positionResult']['resultSize'] for each in result: item = LagouItem() item['url'] = 'https://www.lagou.com/jobs/{}.html'.format(each['positionId']) res = scrapy.Request(url=item['url'], headers=self.headers, callback=self.detail_parse) res.meta['item'] = item res.meta['each'] = each yield res time.sleep(0.1) time.sleep(random.randint(1, 5)) if int(resultSize): self.allpage = int(totalCount) / int(resultSize) + 1 if self.page < self.allpage: self.page += 1 yield FormRequest(self.start_urls[0], headers=self.headers, formdata={ 'first': 'false', 'pn': str(self.page), 'kd': 'Python', 'city': '深圳' }, callback=self.parse ) def detail_parse(self, response): item = response.meta['item'] des = response.css('.job_bt div p::text').extract() item['description'] = ",".join(des) each = response.meta['each'] item['url_obj_id'] = get_md5(item['url']) item['city'] = each['city'] item['company_full_name'] = each['companyFullName'] item['company_size'] = each['companySize'] item['district'] = each['district'] item['education'] = each['education'] item['linestaion'] = each['linestaion'] item['position_name'] = each['positionName'] item['job_nature'] = each['jobNature'] item['salary'] = each['salary'] item['create_time'] = each['createTime'] item['work_year'] = each['workYear'] item["crawl_time"] = datetime.now() yield item