scrapy框架的使用


scrapy簡介

Scrapy 使用了 Twisted異步網絡庫來處理網絡通訊。整體架構大致如下

Scrapy主要包括了以下組件:

  • 引擎(Scrapy)
    用來處理整個系統的數據流處理, 觸發事務(框架核心)
  • 調度器(Scheduler)
    用來接受引擎發過來的請求, 壓入隊列中, 並在引擎再次請求的時候返回. 可以想像成一個URL(抓取網頁的網址或者說是鏈接)的優先隊列, 由它來決定下一個要抓取的網址是什么, 同時去除重復的網址
  • 下載器(Downloader)
    用於下載網頁內容, 並將網頁內容返回給蜘蛛(Scrapy下載器是建立在twisted這個高效的異步模型上的)
  • 爬蟲(Spiders)
    爬蟲是主要干活的, 用於從特定的網頁中提取自己需要的信息, 即所謂的實體(Item)。用戶也可以從中提取出鏈接,讓Scrapy繼續抓取下一個頁面
  • 項目管道(Pipeline)
    負責處理爬蟲從網頁中抽取的實體,主要的功能是持久化實體、驗證實體的有效性、清除不需要的信息。當頁面被爬蟲解析后,將被發送到項目管道,並經過幾個特定的次序處理數據。
  • 下載器中間件(Downloader Middlewares)
    位於Scrapy引擎和下載器之間的框架,主要是處理Scrapy引擎與下載器之間的請求及響應。
  • 爬蟲中間件(Spider Middlewares)
    介於Scrapy引擎和爬蟲之間的框架,主要工作是處理蜘蛛的響應輸入和請求輸出。
  • 調度中間件(Scheduler Middewares)
    介於Scrapy引擎和調度之間的中間件,從Scrapy引擎發送到調度的請求和響應。

Scrapy運行流程大概如下:

  1. 引擎從調度器中取出一個鏈接(URL)用於接下來的抓取
  2. 引擎把URL封裝成一個請求(Request)傳給下載器
  3. 下載器把資源下載下來,並封裝成應答包(Response)
  4. 爬蟲解析Response
  5. 解析出實體(Item),則交給實體管道進行進一步的處理
  6. 解析出的是鏈接(URL),則把URL交給調度器等待抓取

安裝

Linux下的安裝(包括mac)

  pip install scrapy

Windows下的安裝

  a. 下載twisted 
    http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted
  b. 安裝wheel
    pip3 install wheel
  c. 安裝twisted
     進入下載目錄,執行 pip3 install Twisted‑18.7.0‑cp36‑cp36m‑win_amd64.whl
  d. 安裝pywin32
    pip3 install pywin32
  e. 安裝scrapy
    pip3 install scrapy
 

基本命令

 

1.  scrapy startproject 項目名稱

 

    -  在當前目錄中創建一個項目文件(類似於Django)

 

 

 

2.  scrapy genspider [ - t template] <name> <domain>

 

    -  創建爬蟲應用

 

    如:

 

       scrapy gensipider  - t basic oldboy oldboy.com

 

       scrapy gensipider  - t xmlfeed autohome autohome.com.cn

 

     或者簡單直接   scrapy gensipider app名  要爬取的域名

 

    PS:

 

       查看所有命令:scrapy gensipider  - l

 

       查看模板命令:scrapy gensipider  - d 模板名稱

 

 

 

3.  scrapy  list

 

    -  展示爬蟲應用列表

 

 

 

4.  scrapy crawl 爬蟲應用名稱

 

    -  運行單獨爬蟲應用

 

備注:scrapy crawl 應用名稱  表示以日志的形式運行爬蟲應用,可以在后面加 --nolog  取消日志    scrapy crawl 名稱  --nolog

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

1
2
3
4
5
6
7
8
9
10
11
12
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      爬蟲目錄,如:創建文件,編寫爬蟲規則

注意:一般創建爬蟲文件時,以網站域名命名

windows系統編碼錯誤時:
解決方法:
import sys,io
sys.stdout=io.TextIOWrapper(sys.stdout.buffer,encoding='gb18030')
import scrapy

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

    def parse(self, response):
        pass
爬蟲.py
 
備注:可以在settings.py中配置user-agent

在爬取數據時,可以選擇是否往.../robots.txt/發送驗證,是否允許爬取,一般設置為False

使用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)

 

備注: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()

備注:執行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對象。

 

settings.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)

備注:通過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)

 備注:在執行爬蟲應用時,會先執行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)}}}

自動登錄抽屜並點贊和取消贊代碼示例

import scrapy
from scrapy.http.response.html import HtmlResponse
# import sys,os,io
# sys.stdout=io.TextIOWrapper(sys.stdout.buffer,encoding='gb18030')
from ..items import XzxItem
from scrapy.http import Request
from scrapy.http.cookies import CookieJar

class ChoutiSpider(scrapy.Spider):
    name = 'chouti'
    allowed_domains = ['chouti.com']
    start_urls = ['https://dig.chouti.com/r/ask/hot/1']
    cookie_dict = {}
    def start_requests(self):
        for url in self.start_urls:
            yield Request(url=url,callback=self.parse)

    def parse(self, response):
        # 1. 去第一次訪問頁面中獲取cookie
        # print(response.headers['Set-Cookie'],type(response.headers['Set-Cookie']))
        cookie_jar = CookieJar() #
        cookie_jar.extract_cookies(response, response.request) # cookie_jar中包含了cookie


        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
        # 2. 向https://dig.chouti.com/login發送POST請求
        yield Request(
            url='https://dig.chouti.com/login',
            method='POST',
            body="phone=8615901492719&password=qwer1234&oneMonth=1",
            cookies=self.cookie_dict,
            headers={
                'user-agent':'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36',
                'content-type':'application/x-www-form-urlencoded; charset=UTF-8',
            },
            callback=self.check_login
        )

    def check_login(self,response):
        print(response.text)
        yield Request(url='https://dig.chouti.com/',callback=self.index)

    def index(self,response):

        news_id_list = response.xpath('//div[@id="content-list"]//div[@class="part2"]/@share-linkid').extract()
        for news_id in news_id_list:
            #
            """
            news_url = "https://dig.chouti.com/link/vote?linksId=%s" %(news_id,)
            yield Request(
                url=news_url,
                method='POST',
                cookies=self.cookie_dict,
                callback=self.output
            )
            """
            # 取消贊
            news_url = "https://dig.chouti.com/vote/cancel/vote.do"
            yield Request(
                url=news_url,
                method='POST',
                body="linksId=%s" %(news_id,),
                cookies=self.cookie_dict,
                headers={
                    'content-type': 'application/x-www-form-urlencoded; charset=UTF-8',
                },
                callback=self.output
            )

        # 2.1 獲取頁面
        page_list = response.xpath('//*[@id="dig_lcpage"]//a/@href').extract()
        for url in page_list:
            url = "https://dig.chouti.com" + url
            yield Request(url=url,callback=self.index)

    def output(self,response):
        print(response.text)
View Code

備注:爬取過程中的坑:請求頭中,一定要攜帶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