scrapy工作流程


第一步:首先Spiders(爬蟲)將需要發送請求的url(request)經過ScrapyEngine(引擎)交給Scheduler(調度器).

第二步:Scheduler(排序,入隊)處理后,經過ScrapyEngine,DownloaderMiddlewares(可選,主要有User_Agent,Proxy代理)交給Downloader.

第三步:Downloader向互聯網發送請求,斌接受下載響應(response)。將響應(response)經過ScrapyEngine,SpiderMiddlewares(可選)交給Spiders.

第四步:Spiders處理response,提取數據並將數據經過ScrapyEngine交給ItemPipeline保存(可以是本地,可以是數據庫)。循環,提取url重新經過ScrapyEngine交給Scheduler進行下一個循環。知道無Url請求程序停止結束。直到無Url請求程序停止結束

 

 

 

 

 

 項目結構以及爬蟲引用簡介

project_name/

    scrapy.cfg
    project_name /
        __init__.py
        items.py
       middlewares.py
 
        pipelines.py
        settings.py
        spiders /
            __init__.py
            爬蟲 1.py
            爬蟲 2.py
            爬蟲 3.py
文件說明
  • scrapy.cfg  項目的主配置信息。(真正爬蟲相關的配置信息在settings.py文件中)
  • items.py    設置數據存儲模板,用於結構化數據,如:Django的Model
  • pipelines    數據處理行為,如:一般結構化的數據持久化
  • settings.py 配置文件,如:遞歸的層數、並發數,延遲下載等
  • spiders      爬蟲目錄,如:創建文件,編寫爬蟲規則

 

使用scrapy解析文本內容時,可以使用每個應用中的response.xpath(xxx) 進行數據的解析。

print(response.xpath(...))  得到的是一個 Selector對象。selector對象可以繼續xpath進行數據的解析。
備注:xpath使用方法:
  1.//+標簽  表示從全局的子子孫孫中查找標簽    
  2./+標簽   表示從子代中查找標簽
  3.查找帶有xxx屬性的標簽:   標簽+[@標簽屬性="值"]   
  4.查找標簽的某個屬性:  /標簽/@屬性  
  5.從當前標簽中查找時:.//+標簽      
response = HtmlResponse(url='http://example.com', body=html,encoding='utf-8')
hxs = HtmlXPathSelector(response)
print(hxs)   # selector對象
hxs = Selector(response=response).xpath('//a')
print(hxs)    #查找所有的a標簽
hxs = Selector(response=response).xpath('//a[2]')
print(hxs)    #查找某一個具體的a標簽    取第三個a標簽
hxs = Selector(response=response).xpath('//a[@id]')
print(hxs)    #查找所有含有id屬性的a標簽
hxs = Selector(response=response).xpath('//a[@id="i1"]')
print(hxs)    # 查找含有id=“i1”的a標簽
# hxs = Selector(response=response).xpath('//a[@href="link.html"][@id="i1"]')
# print(hxs)   # 查找含有href=‘xxx’並且id=‘xxx’的a標簽
# hxs = Selector(response=response).xpath('//a[contains(@href, "link")]')
# print(hxs)   # 查找 href屬性值中包含有‘link’的a標簽
# hxs = Selector(response=response).xpath('//a[starts-with(@href, "link")]')
# print(hxs)   # 查找 href屬性值以‘link’開始的a標簽
# hxs = Selector(response=response).xpath('//a[re:test(@id, "i\d+")]')
# print(hxs)   # 正則匹配的用法   匹配id屬性的值為數字的a標簽
# hxs = Selector(response=response).xpath('//a[re:test(@id, "i\d+")]/text()').extract()
# print(hxs)    # 匹配id屬性的值為數字的a標簽的文本內容
# hxs = Selector(response=response).xpath('//a[re:test(@id, "i\d+")]/@href').extract()
# print(hxs)    #匹配id屬性的值為數字的a標簽的href屬性值
# hxs = Selector(response=response).xpath('/html/body/ul/li/a/@href').extract()
# print(hxs)
# hxs = Selector(response=response).xpath('//body/ul/li/a/@href').extract_first()
# print(hxs)
 
# ul_list = Selector(response=response).xpath('//body/ul/li')
# for item in ul_list:
#     v = item.xpath('./a/span')
#     # 或
#     # v = item.xpath('a/span')
#     # 或
#     # v = item.xpath('*/a/span')
#     print(v)
View Code

備注:xpath中支持正則的使用:    用法  標簽+[re:test(@屬性值,"正則表達式")]

  獲取標簽的文本內容:   /text()     
  獲取第一個值需要  selector_obj.extract_first()    獲取所有的值  selector_obj.extract()  值在一個list中
scrapy的持久化存儲

scrapy的持久化過程分為四個部分

  首先,items定義傳輸的格式,其次,在爬蟲應用中yield這個item對象,pipeline收到yield的item對象,進行持久化操作,這個過程中,settings中要進行相應的配置

items.py

# 規范持久化的格式
import scrapy


class MyspiderItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    url=scrapy.Field()

  爬蟲引用

import scrapy
from myspider.items import MyspiderItem


class ChoutiSpider(scrapy.Spider):
name = 'chouti'
allowed_domains = ['chouti.com']
start_urls = ['https://dig.chouti.com/']

def parse(self, response):
# print(response.text)
a_list = response.xpath('//div[@id="content-list"]//div[@class="part1"]/a[@class="show-content color-chag"]/@href').extract()
for url in a_list:
yield MyspiderItem(url=url)

  pipelines.py

class MyspiderPipeline(object):

    def __init__(self,file_path):
        self.f = None
        self.file_path = file_path

    @classmethod
    def from_crawler(cls,crawler):
        '''
        執行pipeline類時,會先去類中找from_crawler的方法,
        如果有,則先執行此方法,並且返回一個當前類的對象,
        如果沒有,則直接執行初始化方法
        :param crawler:
        :return:
        '''
        # 可以進行一些初始化之前的處理,比如:文件的路徑配置到settings文件中,方便后期的更改。
        file_path = crawler.settings.get('PACHONG_FILE_PATH')
        return cls(file_path)

    def open_spider(self,spider):
        '''
        爬蟲開始時被調用
        :param spider:
        :return:
        '''
        self.f = open(self.file_path,'w',encoding='utf8')

    def process_item(self, item, spider):
        '''
        執行持久化的邏輯操作
        :param item: 爬蟲yield過來的item對象  (一個字典)
        :param spider:  爬蟲對象
        :return:
        '''
        self.f.write(item['url']+'\n')
        self.f.flush()   #將寫入到內存的文件強刷到文件中,防止夯住,不使用此方法會夯住
        return item

    def close_spider(self,spider):
        '''
        爬蟲結束時調用
        :param spider: 
        :return: 
        '''
        self.f.close()
View Code

備注:執行pipeline時,會先找from_crawler方法,這個方法中,我們可以設置一些settings文件中的配置,通過crawler.settings得到一個settings對象(配置文件對象) <scrapy.settings.Settings object at 0x000002525581F908>

  執行pipeline中的process_item() 方法進行數據的持久化處理時,如果有多個pipeline(比如:將數據分別寫入文件和數據庫)時,先執行的pipeline(按照配置文件中數值的大小順序執行),必須返回一個item對象,否則,后續的pipeline執行時,接收的item為None,無法進行數據的持久化操作,如果只是單純的對某些數據進行一個持久化的處理,可以通過拋出異常,來阻止當前item對象后續的pipeline執行。拋出異常為:from scrapy.exceptions import DropItem       直接  raise DropItem()

  return不返回item對象與拋異常的區別:無返回值或者返回值為None時,后續的pipeline會執行,只是,此時的item為None,而拋出異常,會跳過當前對象后續的pipeline,執行下一個item對象。

 

setting.py

ITEM_PIPELINES = {  
  'myspider.pipelines.MyspiderPipeline': 300,
  'xxxxx.pipelines.FilePipeline': 400, 
} # 每行后面的整型值,確定了他們運行的順序,item按數字從低到高的順序,通過pipeline,通常將這些數字定義在0-1000范圍內。
備注:數值小的先執行。

獲取所有頁面
import scrapy
from  myspider.items import MyspiderItem
from scrapy.http import Request

class ChoutiSpider(scrapy.Spider):
    name = 'chouti'
    allowed_domains = ['chouti.com']
    start_urls = ['https://dig.chouti.com/']

    def parse(self, response):
        a_list = response.xpath('//div[@id="content-list"]//div[@class="part1"]/a[@class="show-content color-chag"]/@href').extract()
        for url in a_list:
            yield MyspiderItem(url=url)

        # 獲取分頁的url
        url_list = response.xpath('//div[@id="dig_lcpage"]//a/@href').extract()
        for url in url_list:
            url = 'https://dig.chouti.com%s'%url
            yield Request(url=url,callback=self.parse)
View Code

備注:通過yield 每一個request對象,將所有的頁面url添加到調度器中。

  scrapy框架會默認的將所有的結果進行去重操作。如果不去重,可以在request參數中,設置  dont_filter=True

  注意:settings.py中設置DEPTH_LIMIT = 1來指定“遞歸”的層數   ,這里的層數不是頁碼數
 
 在生成的每一個爬蟲應用中,會有一個起始url,start_urls = ['https://dig.chouti.com/'],這個起始url執行完后會被parse回調函數接收響應結果。那我們如何修改這個回調函數呢?
  其實,在每一個爬蟲應用繼承的父類中,會執行一個方法  start_requests ,這個方法,會將起始的url生成一個request對象,傳給調度器。
class Spider(object_ref):

        def start_requests(self):
        cls = self.__class__
        if method_is_overridden(cls, Spider, 'make_requests_from_url'):
            warnings.warn(
                "Spider.make_requests_from_url method is deprecated; it "
                "won't be called in future Scrapy releases. Please "
                "override Spider.start_requests method instead (see %s.%s)." % (
                    cls.__module__, cls.__name__
                ),
            )
            for url in self.start_urls:
                yield self.make_requests_from_url(url)
        else:
            for url in self.start_urls:
                yield Request(url, dont_filter=True)
View Code

備注:在執行爬蟲應用時,會先執行start_requests方法,所以我們可以重寫此方法自定制。

 

獲取響應數據中的cookie

返回的response中,無法通過  .cookies 獲取cookie,只能通過從響應頭中獲取,但是獲取的結果還得需要解析.

{b'Server': [b'Tengine'], b'Content-Type': [b'text/html; charset=UTF-8'], b'Date': [
b'Fri, 20 Jul 2018 13:43:42 GMT'], b'Cache-Control': [b'private'], b'Content-Language': [b'en'], 
b'Set-Cookie': [b'gpsd=5b05bcae8c6f4a273a53addfc8bbff22; domain=chouti.com; path=/; expires=Sun, 
19-Aug-2018 13:43:42 GMT', b'JSESSIONID=aaadbrXmU-Jh2_kvbaysw; path=/'], b'Vary': [b'Accept-Encoding'],
 b'Via': [b'cache15.l2nu29-1[69,0], kunlun9.cn125[73,0]'], b'Timing-Allow-Origin': [b'*'],
 b'Eagleid': [b'6a78b50915320942226762320e']}

  所以,要通過scrapy封裝的方法,將cookie解析出來

import scrapy
from scrapy.http.cookies import CookieJar

class ChoutiSpider(scrapy.Spider):
    name = 'chouti'
    allowed_domains = ['chouti.com']
    start_urls = ['https://dig.chouti.com/']
    cookie_dict = {}
    def parse(self, response):

        cookie_jar = CookieJar()
        cookie_jar.extract_cookies(response,response.request)   
        for k, v in cookie_jar._cookies.items():
            for i, j in v.items():
                for m, n in j.items():
                    self.cookie_dict[m] = n.value
        print(self.cookie_dict)

  備注:CookieJar中封裝的內容特別豐富,print(cookie_jar._cookies)   包含很多

{'.chouti.com': {'/': {'gpsd': Cookie(version=0, name='gpsd', value='fcb9b9da7aaede0176d2a88cde8b6adb',
 port=None, port_specified=False, domain='.chouti.com', domain_specified=True, domain_initial_dot=False, 
path='/', path_specified=True, secure=False, expires=1534688487, discard=False, comment=None, 
comment_url=None, rest={}, rfc2109=False)}}, 'dig.chouti.com': {'/': {'JSESSIONID': 
Cookie(version=0, name='JSESSIONID', value='aaa4GWMivXwJf6ygMaysw', port=None, port_specified=False, 
domain='dig.chouti.com', domain_specified=False, domain_initial_dot=False, path='/',
 path_specified=True, secure=False, expires=None, discard=True, comment=None, comment_url=None, rest={}, 
rfc2109=False)}}}

 

備注:爬取過程中的坑:請求頭中,一定要攜帶content-type參數。請求過程中的url不能重復,尤其是和起始url。

我們可以使用urllib中的urlencode幫我們把數據轉化為formdata格式的.

from urllib.parse import urlencode

ret = {'name':'xxx','age':18}

print(urlencode(ret))

  

 

 轉載:

 



 


免責聲明!

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



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