1. 程式人生 > >CrawlScrapy框架爬取Boos直聘職位資訊

CrawlScrapy框架爬取Boos直聘職位資訊

寫在前面和推薦學習

零基礎:21天搞定Python分佈爬蟲
在本文中使用CrawlScrapy框架結合代理來實現對Boos直聘職位資訊的爬取。

簡單說明Scrapy框架

在這裡插入圖片描述

主要流程
1.爬蟲傳送一個請求給引擎
2.引擎將這個請求傳送給排程器
3.排程器按照一定的方式進行整理,在將請求傳送給引擎
4.引擎再次將請求傳送給下載器中介軟體,去到網路中請求資源進行下載
5.下載之後封裝為Response物件,返回給引擎
6.引擎在將Resonse傳送給爬蟲提取資訊,如果請求正常這時爬蟲返回一個ITEMS,否則返回一個Resqust物件給引擎,再次進入排程器
7.這個items/Request返回給引擎,引擎將ITEMS物件傳送給Pipeline對資料進行儲存

爬蟲結構

本次爬蟲使用的是CrawlScrapy框架,這個框架繼承了Scrapy框架,流程一致,不同的是,CrawlScrapy框架中有一個連結提取器,通過正則表示式進行匹配頁面中所有的URL,有點廣度優先的思想。
連結提取器如下:

rules = (
        #c101010100/?query=資料分析&page=2
        Rule(LinkExtractor(allow=r'.+/\?query=python&page=\d'), follow=True),
        #https://www.zhipin.com/job_detail/9f625f605d18d1661XN_39--GFU~.html
        Rule(LinkExtractor(allow=r'.+/job_detail/.+'), callback='parse_item', follow=False),
    )

在此次爬蟲中主要實現瞭如下幾個部分:

  1. 爬蟲部分,解析提取網頁
  2. 實現了連結提取器中的規則,確定了需要爬取那些網頁
  3. 實現了下載器中介軟體部分,在該部分添加了代理的功能,這樣即使IP被封也可以繼續爬取資訊
  4. 實現了Pipeline的部分,對獲取到的資料進行的儲存,將資料儲存到了JSON檔案中

爬蟲部分

實現了一個類,繼承了CrawlSpider類,再定義規則時,主要獲取列表頁資訊和詳情頁資訊,再列表頁中獲取詳情頁的URL,主要對詳情頁的資訊進行解析,提取詳情頁中的主要資訊,返回ITEMS
程式碼如下:

	class BossSpiderSpider(CrawlSpider):
    name = 'boss_spider'
    allowed_domains = ['www.zhipin.com']
    start_urls = ['https://www.zhipin.com/job_detail/?query=python&page=1']

    rules = (
    	#匹配列表頁URL
        #c101010100/?query=資料分析&page=2
        Rule(LinkExtractor(allow=r'.+/\?query=python&page=\d'), follow=True),
        #匹配詳情頁URL
        #https://www.zhipin.com/job_detail/9f625f605d18d1661XN_39--GFU~.html
        Rule(LinkExtractor(allow=r'.+/job_detail/.+'), callback='parse_item', follow=False),
    )

    def parse_item(self, response):
        title = response.xpath("//div[@class='name']/h1/text()").get()
        salary = response.xpath("//span[@class='badge']/text()").get()
        if salary:
            salary = salary.strip()
        else:
            salary = ''
        company_div = response.xpath("//div[@class='info-company']")
        company_name = company_div.xpath("./h3[@class='name']/a/text()").get()
        company_info = company_div.xpath("./p[1]//text()").getall()
        primary_info = response.xpath("//div[@class='info-primary']/p[1]/text()").getall()
        city = primary_info[0]
        work_time = primary_info[1]
        education = primary_info[2]
        datail_count = response.xpath("//div[@class='detail-content']//div[@class='text']/text()").getall()
        
        item = BoosDemoItem(
            title = title,
            salary = salary,
            company_name = company_name,
            company_info = company_info,
            city = city,
            work_time = work_time,
            education = education,
            datail_count = datail_count
        )
        yield item

下載器中介軟體部分

在下載器中介軟體這裡實現了兩個類,分別為請求頭中介軟體UserAgentMiddleware,和IP代理中介軟體IPProxyMiddlewares
請求頭中介軟體:
在列表中儲存多個請求頭,在每次請求的時候隨機選擇一個。

    #process_request介面會在請求開始的時候執行
    def process_request(self,request,spider):
        User_Agent = random.choice(self.Useragent)
        request.headers['User-Agent'] = User_Agent

IP代理中介軟體:
判斷每次URL失效的時候重新請求一個IP代理。重新返回爬取

	class IPProxyMiddlewares(object):
    def __init__(self):
        self.currenu_proxy = None
        self.lock = DeferredLock()

    def process_request(self,request,spider):
        if 'proxy' not in request.meta or self.currenu_proxy.is_expire:
            self.update_proxy()
        request.meta['proxy'] = self.currenu_proxy.proxy

    def update_proxy(self):
        self.lock.acquire()
        if not self.currenu_proxy or self.currenu_proxy.is_expire or self.currenu_proxy.block:
            self.url = 'http://webapi.http.zhimacangku.com/getip?num=1&type=2&pro=&city=0&yys=0&port=11&time=1&ts=1&ys=0&cs=0&lb=1&sb=0&pb=4&mr=1&regions='

            resp = requests.get(url=self.url)
            info_text = resp.text
            info = json.loads(info_text)
            if len(info['data'])>0:
                data = info['data'][0]
                self.currenu_proxy = Ipproxy(data)
        self.lock.release()
	#代理請求之後返回的時候執行
    def process_response(self,request,response,spider):
        if 'captcha' in response.url or response.status != 200 :
            if not self.currenu_proxy.block:
                self.currenu_proxy.block = True
            self.update_proxy()
            #request.meta['proxy'] = self.currenu_proxy.get_proxy()
            return request
        return response
        
class Ipproxy(object):
    def __init__(self,data):

        self.ip = data['ip']
        self.port = data['port']
        expire_time_str = data['expire_time']
        print(self.ip, self.port, expire_time_str)
        self.block = False
        # 117.94.113.20 4734 2018-10-18 23:23:13
        year, month, day = expire_time_str.split()[0].split('-')
        hour, minute, scend = expire_time_str.split()[1].split(':')
        self.expire_time = datetime(year=int(year), month=int(month), day=int(day), hour=int(hour), minute=int(minute),second=int(scend))

        self.proxy = "https://{}:{}".format(self.ip,self.port)

    @property
    def is_expire(self):
        if (self.expire_time - datetime.now()) < timedelta(seconds=5):
            return True
        else:
            return False

Pipeline的部分

開啟一個JSON檔案,將資料儲存到JSON檔案中

#按行寫入資料
from scrapy.exporters import JsonLinesItemExporter

class BoosDemoPipeline(object):
    def __init__(self):
        self.boss_fp = open("boss_python_fp.json",'wb')
        self.boos_exporter = JsonLinesItemExporter(self.boss_fp,ensure_ascii = False)
    def process_item(self, item, spider):
        self.boos_exporter.export_item(item)

    def close_item(self,spider):
        self.boss_fp.close()

推薦學習

知了課堂零基礎:21天搞定Python分佈爬蟲