爬蟲框架之Scrapy——爬取某招聘信息網站


案例1:爬取內容存儲為一個文件

1.建立項目

C:\pythonStudy\ScrapyProject>scrapy startproject tenCent
New Scrapy project 'tenCent', using template directory 'c:\\program files\\pytho
n36\\lib\\site-packages\\scrapy\\templates\\project', created in:
    C:\pythonStudy\ScrapyProject\tenCent

You can start your first spider with:
    cd tenCent
    scrapy genspider example example.com

2.編寫item文件

import scrapy


class TencentItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    # 職位名稱
    position_name = scrapy.Field()
    # 詳情鏈接
    position_link = scrapy.Field()
    # 職位類別
    position_type = scrapy.Field()
    # 職位人數
    position_number = scrapy.Field()
    # 職位地點
    work_location = scrapy.Field()
    # 發布時間
    publish_times = scrapy.Field()
    # 工作職責
    position_duty = scrapy.Field()
    # 工作要求
    position_require = scrapy.Field()

3.建立spider文件

C:\pythonStudy\ScrapyProject\tenCent\tenCent\spiders>scrapy genspider tencent "hr.tencent.com" Created spider 'tencent' using template 'basic' in module:
  tenCent.spiders.tencent

編寫spider類邏輯

from tenCent.items import TencentItem


class TencentSpider(scrapy.Spider):
    name = 'tencent'
    allowed_domains = ['hr.tencent.com']
    base_url = 'https://hr.tencent.com/'
    start_urls = ['https://hr.tencent.com/position.php']

    def parse(self, response):
        node_list = response.xpath('//tr[@class="even"] | //tr[@class="odd"]')
        # 選取所有標簽tr 且class屬性等於even或odd的元素
        next_page = response.xpath('//a[@id="next"]/@href').extract_first()
        # 選取所有標簽a且id=next,href屬性值

        for node in node_list:
            '''
            實例化對象要放在循環里面,否則會造成item被多次賦值,
            因為每次循環完畢后,請求只給了調度器,入隊,並沒有去執行請求,
            循環完畢后,下載器會異步執行隊列中的請求,此時item已經為最后一條記錄,
            而詳細內容根據url不同去請求的,所以每條詳細頁是完整的,
            最終結果是數據內容為每頁最后一條,詳細內容與數據內容不一致,
            在yield item后,會把內容寫到pipeline中
            '''
            item = TencentItem()

            item['position_name'] = node.xpath('./td[1]/a/text()').extract_first()  # 獲取第一個td標簽下a標簽的文本
            item['position_link'] = node.xpath('./td[1]/a/@href').extract_first()  # 獲取第一個td標簽下a標簽href屬性
            item['position_type'] = node.xpath('./td[2]/text()').extract_first()  # 獲取第二個td標簽下文本
            item['position_number'] = node.xpath('./td[3]/text()').extract_first()  # 獲取第3個td標簽下文本
            item['work_location'] = node.xpath('./td[4]/text()').extract_first()  # 獲取第4個td標簽下文本
            item['publish_times'] = node.xpath('./td[5]/text()').extract_first()  # 獲取第5個td標簽下文本
            # yield item  注釋yield item ,因為detail方法中yield item會覆蓋這個
            yield scrapy.Request(url=self.base_url + item['position_link'] ,callback=self.detail,meta={'item':item})  # 請求詳細頁,把item傳到detail
            # 請求給調度器,入隊,循環結束完成后,交給下載器去異步執行,返回response
        yield scrapy.Request(url=self.base_url + next_page,callback=self.parse) # 請求下一頁




    def detail(self, response):
        """
        爬取詳細內容
        :param response:
        :return:
        """
        print("-->detail")
        item = response.meta['item'] # 得到parse中的yield item
        item['position_duty'] =  ''.join(response.xpath('//ul[@class="squareli"]')[0].xpath('./li/text()').extract())  # 轉化為字符串
        item['position_require'] = ''.join(response.xpath('//ul[@class="squareli"]')[1].xpath('./li/text()').extract()) # 轉化為字符串

        yield item

 

4.建立pipeline文件

存儲數據

# -*- coding: utf-8 -*-

# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://doc.scrapy.org/en/latest/topics/item-pipeline.html

import json
class TencentPipeline(object):
    def open_spider(self, spider):
        """
         # spider (Spider 對象) – 被開啟的spider
         # 可選實現,當spider被開啟時,這個方法被調用。
        :param spider:
        :return:
        """
        self.file = open('tencent.json', 'w', encoding='utf-8')
        json_header = '{ "tencent_info":['
        self.count = 0
        self.file.write(json_header)  # 保存到文件

    def close_spider(self, spider):
        """
        # spider (Spider 對象) – 被關閉的spider
        # 可選實現,當spider被關閉時,這個方法被調用
        :param spider:
        :return:
        """
        json_tail = '] }'
        self.file.seek(self.file.tell() - 1)  # 定位到最后一個逗號
        self.file.truncate()  # 截斷后面的字符
        self.file.write(json_tail)  # 添加終止符保存到文件
        self.file.close()

    def process_item(self, item, spider):
        """
        # item (Item 對象) – 被爬取的item
        # spider (Spider 對象) – 爬取該item的spider
        # 這個方法必須實現,每個item pipeline組件都需要調用該方法,
        # 這個方法必須返回一個 Item 對象,被丟棄的item將不會被之后的pipeline組件所處理。

        :param item:
        :param spider:
        :return:
        """

        content = json.dumps(dict(item), ensure_ascii=False, indent=2) + ","  # 字典轉換json字符串
        self.count += 1
        print('content', self.count)
        self.file.write(content)  # 保存到文件

 

5.設置settiing

# Crawl responsibly by identifying yourself (and your website) on the user-agent
USER_AGENT = '"User-Agent":"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36"'  # 頭部信息,反爬

ITEM_PIPELINES = {
   'tenCent.pipelines.TencentPipeline': 300,
}

 

6.執行程序

C:\pythonStudy\ScrapyProject\tenCent\tenCent\spiders>scrapy crawl tencent

json文件

 

案例2:爬取內容存儲為兩個文件

案例2與只是把案例1中的概率頁和詳細內容頁分成兩個文件去存儲,

只有某些py文件內容有變化,以下只列舉出有變化的py文件

1.編寫item文件

用兩個類表示不同的存儲內容

import scrapy

"""
職位概覽頁字段
"""
class TencentItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    # 職位名稱
    position_name = scrapy.Field()
    # 詳情鏈接
    position_link = scrapy.Field()
    # 職位類別
    position_type = scrapy.Field()
    # 職位人數
    position_number = scrapy.Field()
    # 職位地點
    work_location = scrapy.Field()
    # 發布時間
    publish_times = scrapy.Field()

"""
職位詳細頁字段
"""
class TenDetailItem(scrapy.Item):
    # 工作職責
    position_duty = scrapy.Field()
    # 工作要求
    position_require = scrapy.Field()

 

2.編寫spider文件邏輯

# -*- coding: utf-8 -*-
import scrapy


from tenCent.items import TencentItem
from tenCent.items import TenDetailItem


print(__name__)
class TencentSpider(scrapy.Spider):
    name = 'tencent'
    allowed_domains = ['hr.tencent.com']
    base_url = 'https://hr.tencent.com/'
    start_urls = ['https://hr.tencent.com/position.php']

    def parse(self, response):
        node_list = response.xpath('//tr[@class="even"] | //tr[@class="odd"]')
        # 選取所有標簽tr 且class屬性等於even或odd的元素
        next_page = response.xpath('//a[@id="next"]/@href').extract_first()
        # 選取所有標簽a且id=next,href屬性值

        for node in node_list:
            '''
            實例化對象要放在循環里面,否則會造成item被多次賦值,
            因為每次循環完畢后,請求只給了調度器,入隊,並沒有去執行請求,
            循環完畢后,下載器會異步執行隊列中的請求,此時item已經為最后一條記錄,
            而詳細內容根據url不同去請求的,所以每條詳細頁是完整的,
            最終結果是數據內容為每頁最后一條,詳細內容與數據內容不一致,
            在yield item后,會把內容寫到pipeline中
            '''
            item = TencentItem()

            item['position_name'] = node.xpath('./td[1]/a/text()').extract_first()  # 獲取第一個td標簽下a標簽的文本
            item['position_link'] = node.xpath('./td[1]/a/@href').extract_first()  # 獲取第一個td標簽下a標簽href屬性
            item['position_type'] = node.xpath('./td[2]/text()').extract_first()  # 獲取第二個td標簽下文本
            item['position_number'] = node.xpath('./td[3]/text()').extract_first()  # 獲取第3個td標簽下文本
            item['work_location'] = node.xpath('./td[4]/text()').extract_first()  # 獲取第4個td標簽下文本
            item['publish_times'] = node.xpath('./td[5]/text()').extract_first()  # 獲取第5個td標簽下文本
            yield item
            yield scrapy.Request(url=self.base_url + item['position_link'] ,callback=self.detail)  # 請求詳細頁
            # 請求給調度器,入隊,循環結束完成后,交給下載器去異步執行,返回response
        # yield scrapy.Request(url=self.base_url + next_page,callback=self.parse) # 請求下一頁


    def detail(self, response):
        """
        爬取詳細內容
        :param response:
        :return:
        """
        print("-->detail")
        item = TenDetailItem() # 實例化TenDetailItem
        item['position_duty'] = ''.join(response.xpath('//ul[@class="squareli"]')[0].xpath('./li/text()').extract())  # 轉化為字符串
        item['position_require'] = ''.join(response.xpath('//ul[@class="squareli"]')[1].xpath('./li/text()').extract()) # 轉化為字符串

        yield item

 

3.建立pipeline文件

存儲數據

# -*- coding: utf-8 -*-

# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://doc.scrapy.org/en/latest/topics/item-pipeline.html


import json
from .items import TencentItem
from .items import TenDetailItem
"""
存儲職位概覽
"""
class TencentPipeline(object):
    def open_spider(self, spider):
        """
         # spider (Spider 對象) – 被開啟的spider
         # 可選實現,當spider被開啟時,這個方法被調用。
        :param spider:
        :return:
        """
        self.file = open('tencent.json', 'w', encoding='utf-8')
        json_header = '{ "tencent_info":['
        self.count = 0
        self.file.write(json_header)  # 保存到文件

    def close_spider(self, spider):
        """
        # spider (Spider 對象) – 被關閉的spider
        # 可選實現,當spider被關閉時,這個方法被調用
        :param spider:
        :return:
        """
        json_tail = '] }'
        self.file.seek(self.file.tell() - 1)  # 定位到最后一個逗號
        self.file.truncate()  # 截斷后面的字符
        self.file.write(json_tail)  # 添加終止符保存到文件
        self.file.close()

    def process_item(self, item, spider):
        """
        # item (Item 對象) – 被爬取的item
        # spider (Spider 對象) – 爬取該item的spider
        # 這個方法必須實現,每個item pipeline組件都需要調用該方法,
        # 這個方法必須返回一個 Item 對象,被丟棄的item將不會被之后的pipeline組件所處理。

        :param item:
        :param spider:
        :return:
        """
        if isinstance(item,TencentItem):
            content = json.dumps(dict(item), ensure_ascii=False, indent=2) + ","  # 字典轉換json字符串
            self.count += 1
            print('content', self.count)
            self.file.write(content)  # 保存到文件
        '''
        return item后,item會根據優先級
        傳遞到下一個管道TenDetailPipeline處理
        此段代碼說明當實例不屬於TencentItem時,放棄存儲json,
        直接傳遞到下一個管道處理
        return放在if外面,如果寫在if里面item在不屬於TencentItem實例后,
        item會終止傳遞,造成detail數據丟失
        '''
        return item

"""
存儲職位詳細情況
"""
class TenDetailPipeline(object):
    def open_spider(self, spider):
        """
         # spider (Spider 對象) – 被開啟的spider
         # 可選實現,當spider被開啟時,這個方法被調用。
        :param spider:
        :return:
        """
        self.file = open('tendetail.json', 'w', encoding='utf-8')
        json_header = '{ "tendetail_info":['
        self.count = 0
        self.file.write(json_header)  # 保存到文件

    def close_spider(self, spider):
        """
        # spider (Spider 對象) – 被關閉的spider
        # 可選實現,當spider被關閉時,這個方法被調用
        :param spider:
        :return:
        """
        json_tail = '] }'
        self.file.seek(self.file.tell() - 1)  # 定位到最后一個逗號
        self.file.truncate()  # 截斷后面的字符
        self.file.write(json_tail)  # 添加終止符保存到文件
        self.file.close()

    def process_item(self, item, spider):
        """
        # item (Item 對象) – 被爬取的item
        # spider (Spider 對象) – 爬取該item的spider
        # 這個方法必須實現,每個item pipeline組件都需要調用該方法,
        # 這個方法必須返回一個 Item 對象,被丟棄的item將不會被之后的pipeline組件所處理。

        :param item:
        :param spider:
        :return:
        """
        if isinstance(item, TenDetailItem):
            '''
            得到item,判斷item實例屬於TenDetailItem,存儲json文件
            如果不屬於,直接return item到下一個管道
          '''
            print('**'*30)
            content = json.dumps(dict(item), ensure_ascii=False, indent=2) + ","  # 字典轉換json字符串
            self.count += 1
            print('content', self.count)
            self.file.write(content)  # 保存到文件
        return item

 

4.設置settiing

# See https://doc.scrapy.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = { # 注冊2個管道
   'tenCent.pipelines.TencentPipeline': 300,
   'tenCent.pipelines.TenDetailPipeline':400  # 數字越大,優先級越小,最后被執行
}

 

5.執行

#>scrapy crawl tencent >1.txt 2>&1
#把內容輸出到文件中

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM