1. 程式人生 > >【Scrapy】CrawlSpider 單頁面Ajax爬取

【Scrapy】CrawlSpider 單頁面Ajax爬取

專案目標

爬取拉勾網職位列表基本資訊+職位描述

專案思考

拉勾網的招聘崗位列表,這是Ajax非同步載入的。

我想把崗位列表所顯示的資訊爬取下來,同時還需要崗位的工作詳情。

爬取流程就是一開始就不斷獲取職位列表的json,然後從json中提取對應的職位詳情頁,再進去爬取其職位描述。

使用Scrapy的scrapy.Spider基礎爬蟲模板很簡單就可以實現,直接過載編寫parse方法,再加上個回撥方法就可以。

但如何使用CrawlSpider做到類似的功能?

對於獲取json的網址,rules規則沒什麼用,而CrawlSpider中的parse是不能被過載的。

  1. 第一步,重寫start_request():

    crawlspider繼承基類是spider,所以它的開始入口也是start_request(),然後預設回撥parse。注意回撥parse這個不能改。

  2. 第二步,重寫parse_start_url()

    start_request()->parse()->_parse_response()->parse_start_url()

    如果設定了callback就會呼叫parse_start_url()方法,rules中的回撥。

    因為rules在本次專案中沒有作用,所以我們需要過載parse_start_url()作為我們的回撥方法。

    在parse_start_url()中,需要獲取職位的詳情頁,發起request,設定回撥方法。
    不斷髮起下一頁的職位列表請求。

  3. 第三步,編寫解析職位描述的detail_parse()

    注意的時,這裡最後在parse_start_url()中使用response.meta傳遞item到detail_parse()來進行資料儲存。

    因為如果在parse_start_url()就把職位列表的資訊儲存下來的話,因為Scrapy程式排程的關係,在插入資料庫的時候,item中的資料不一定同步,插入資料庫會報一些錯。

  4. 注意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