1. 程式人生 > 其它 >Python爬蟲之scrapy框架基礎理解

Python爬蟲之scrapy框架基礎理解

目錄

1 scrapy

1.1 簡介

scrapy框架Python編寫 ,是 一個快速、高層次的螢幕抓取和 web 抓取框架,用於抓取 web 站點並從頁面中提取結構化的資料。
Scrapy 用途廣泛,可以用於資料探勘、監測和自動化測試,還有高效能的持久化儲存,非同步的資料下載,高效能的資料解析,分散式等等

1.1.1 scrapy原理

scrapy有五大核心元件:

  • 引擎:用來處理整個系統的資料流處理,觸發事務(框架核心)
  • 排程器:用來接受引擎發過來的請求,壓入佇列中,並在引擎再次請求的時候返回,可以想象成一個URL的優先佇列,由它來決定下一個喜歡的網址是什麼,同時去除重複的網址
  • 下載器:用於下載網頁內容,並將網頁內容返回,scrapy下載是建立在twisted這個高效的非同步模型上的
  • 爬蟲:爬蟲主要幹活的,用於從特定網頁中提取自己想要的資訊,使用者也可以從中爬取出連結,讓Scrapy爬取下一個頁面
  • 管道:負責處理頁面中提取的實體,主要功能是持久化實體,驗證實體的有效性,清除不需要資訊,當頁面被爬蟲解析後,將解析傳送到專案管道,並經過幾個特定次序處理資料

1.2 環境安裝

mac或者linux系統直接:pip install scrapy
windows系統,會比較麻煩:

  • 首先安裝wheelpip install wheel

  • 下載twistedhttps://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted,下載對應的whl檔案。如下圖,cp後面是python版本,amd64代表64位,劃線的是我要下載的,點選下載

  • 在下載後的twisted資料夾內執行pip命令來安裝:pip install Twisted-20.3.0-cp38-cp38-win_amd64.whl
    如果安裝報錯:

ERROR: Exception:
Traceback (most recent call last):
File "d:\software\codelanguages\python\python-file\python-3.9.4-embed-amd64\lib\site-packages\pip_internal\cli\base_command.py", line 173, in _main
status = self.run(options, args)
File "d:\software\codelanguages\python\python-file\python-3.9.4-embed-amd64\lib\site-packages\pip_internal\cli\req_command.py", line 203, in wrapper
return func(self, options, args)
File "d:\software\codelanguages\python\python-file\python-3.9.4-embed-amd64\lib\site-packages\pip_internal\commands\install.py", line 315, in run
requirement_set = resolver.resolve(
File "d:\software\codelanguages\python\python-file\python-3.9.4-embed-amd64\lib\site-packages\pip_internal\resolution\resolvelib\resolver.py", line 75, in resolve
collected = self.factory.collect_root_requirements(root_reqs)
File "d:\software\codelanguages\python\python-file\python-3.9.4-embed-amd64\lib\site-packages\pip_internal\resolution\resolvelib\factory.py", line 471, in collect_root_requirements
req = self._make_requirement_from_install_req(
File "d:\software\codelanguages\python\python-file\python-3.9.4-embed-amd64\lib\site-packages\pip_internal\resolution\resolvelib\factory.py", line 433, in _make_requirement_from_install_req
cand = self._make_candidate_from_link(
File "d:\software\codelanguages\python\python-file\python-3.9.4-embed-amd64\lib\site-packages\pip_internal\resolution\resolvelib\factory.py", line 204, in _make_candidate_from_link
self._link_candidate_cache[link] = LinkCandidate(
File "d:\software\codelanguages\python\python-file\python-3.9.4-embed-amd64\lib\site-packages\pip_internal\resolution\resolvelib\candidates.py", line 295, in init


super().init(
File "d:\software\codelanguages\python\python-file\python-3.9.4-embed-amd64\lib\site-packages\pip_internal\resolution\resolvelib\candidates.py", line 156, in init
self.dist = self._prepare()
File "d:\software\codelanguages\python\python-file\python-3.9.4-embed-amd64\lib\site-packages\pip_internal\resolution\resolvelib\candidates.py", line 227, in _prepare
dist = self._prepare_distribution()
File "d:\software\codelanguages\python\python-file\python-3.9.4-embed-amd64\lib\site-packages\pip_internal\resolution\resolvelib\candidates.py", line 305, in _prepare_distribution
return self._factory.preparer.prepare_linked_requirement(
File "d:\software\codelanguages\python\python-file\python-3.9.4-embed-amd64\lib\site-packages\pip_internal\operations\prepare.py", line 508, in prepare_linked_requirement
return self._prepare_linked_requirement(req, parallel_builds)
File "d:\software\codelanguages\python\python-file\python-3.9.4-embed-amd64\lib\site-packages\pip_internal\operations\prepare.py", line 570, in _prepare_linked_requirement
dist = _get_prepared_distribution(
File "d:\software\codelanguages\python\python-file\python-3.9.4-embed-amd64\lib\site-packages\pip_internal\operations\prepare.py", line 61, in _get_prepared_distribution
return abstract_dist.get_pkg_resources_distribution()
File "d:\software\codelanguages\python\python-file\python-3.9.4-embed-amd64\lib\site-packages\pip_internal\distributions\wheel.py", line 26, in get_pkg_resources_distribution
with ZipFile(self.req.local_file_path, allowZip64=True) as z:
File "zipfile.py", line 1257, in init
File "zipfile.py", line 1324, in _RealGetContents
zipfile.BadZipFile: File is not a zip file

可能是檔案受損,重新下載執行上述命令即可成功

  • 安裝pywin32pip install pywin32
  • 安裝scrapypip install scrayp

最後測試是否安裝成功,在DOS終端裡錄入scrapy指令,沒有報錯即表示安裝成功

1.3 使用scrapy

1.3.1 使用步驟

  • 建立工程:scrapy startproject ProjectName
  • 建立爬蟲檔案
    先進入到工程目錄中,然後執行命令scrapy genspider spiderFileName www.xxx.com,就會在spriders子目錄中建立一個爬蟲檔案
    例如執行:scrapy genspider firstSpiderFile www.xxx.com會生成一個檔案firstSpiderFile.py 且內部有字尾為Spider的類
    allowed_domains:允許的域名,限定start_urls列表中哪些url可以傳送請求,如果註釋掉,會全部發送
import scrapy

class FirstspiderfileSpider(scrapy.Spider):
    #爬蟲檔名字,爬蟲原始檔一個唯一標識
    name = 'firstSpiderFile'
    #允許的域名,限定start_urls列表中哪些url可以傳送請求,如果註釋掉,會全部發送
    #allowed_domains = ['www.baidu.com']
    #起始的url列表:列表中的存放的url被scrapy會自動傳送請求
    start_urls = ['http://www.baidu.com/']

    #用於資料解析:response引數表示請求成功後對應的響應物件,如果解析返回物件有多個,那麼這個pass方法就要呼叫多次
    def parse(self, response):
        print(response)

  • 執行工程,輸入爬蟲檔案spiderFileName scrapy crawl spiderFileName
  • 在爬蟲檔案中新增相關程式碼

注意:在爬蟲檔案使用xpath解析時,返回可能不是字串,而是Selector物件,就可以用extract()方法解析`

1.4 持久化儲存

1.4.1 基於終端指令

基於終端指令,只可以將parse方法的返回值儲存到本地的文字檔案中,但是隻支援一下幾種格式:'json', 'jsonlines', 'jl', 'csv', 'xml', 'marshal', 'pickle'
使用如下命令,就是在執行命令增加一個-o引數:

scrapy crawl spiderFileName -o 檔案路徑+檔名

import scrapy

class FirstspiderfileSpider(scrapy.Spider):
    #爬蟲檔名字,爬蟲原始檔一個唯一標識
    name = 'firstSpiderFile'
    #允許的域名,限定start_urls列表中哪些url可以傳送請求,如果註釋掉,會全部發送
    #allowed_domains = ['www.baidu.com']
    #起始的url列表:列表中的存放的url被scrapy會自動傳送請求
    start_urls = ['https://www.test.com/chaxun/zuozhe/77.html']

    #用於資料解析:response引數表示請求成功後對應的響應物件,如果解析返回物件有多個,那麼這個pass方法就要呼叫多次
    def parse(self, response):
        div_list = response.xpath('//div[@class="shici_list_main"]')
        all_data=[]
        for div in div_list:
            #此處使用相對路徑
            shici_name=div.xpath("h3/a/text()")[0].extract()
            # print(shici_name)
            shici_text_list=div.xpath("div//text()").extract()
            shici_text_list = "".join(shici_text_list)
            dict={
                'title':shici_name,
                "content":shici_text_list
            }
            all_data.append(dict)

        return all_data

注意:在爬蟲檔案的parse方法需要有返回值來儲存檔案

1.4.2 基於管道

1.4.2.1 基於管道步驟

基於管道大致步驟:(即把要寫的程式碼寫到items.py檔案中)

  • item類中定義相關的屬性(items.py)
  • 把要解析的資料封裝儲存到item類型物件(items.py)
  • item類的資料物件items.py提交給管道pipelines.py進行持久化儲存操作
  • 在管道類的process_item中藥將其接收到item物件中儲存的資料進行持久化儲存操作
  • 在配置檔案settings.py中開啟管道

1.4.2.2 基於管道操作

item類中定義相關的屬性

import scrapy
class FirstItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    title=scrapy.Field()
    content=scrapy.Field()

把要解析的資料封裝儲存到item類型物件
注意:此處的爬蟲檔案是不需要return但是需要yield

import scrapy
from first.items import FirstItem

class FirstspiderfileSpider(scrapy.Spider):
    #爬蟲檔名字,爬蟲原始檔一個唯一標識
    name = 'firstSpiderFile'
    #允許的域名,限定start_urls列表中哪些url可以傳送請求,如果註釋掉,會全部發送
    #allowed_domains = ['www.baidu.com']
    #起始的url列表:列表中的存放的url被scrapy會自動傳送請求
    start_urls = ['https://www.test.com/chaxun/zuozhe/77.html']

    #用於資料解析:response引數表示請求成功後對應的響應物件,如果解析返回物件有多個,那麼這個pass方法就要呼叫多次
    def parse(self, response):
        div_list = response.xpath('//div[@class="shici_list_main"]')
        all_data=[]
        for div in div_list:
            #此處使用相對路徑
            shici_name=div.xpath("h3/a/text()")[0].extract()
            # print(shici_name)
            shici_text_list=div.xpath("div//text()").extract()
            shici_text_list = "".join(shici_text_list)
            
            item = FirstItem()
            item['title']=shici_name
            item['content']=shici_text_list
            yield item

pipelines.py開始持久化操作
注意:process_item中的return item,會把item傳遞給下一個即將被執行的管道類,不管後面有沒有管道盡量都return

from itemadapter import ItemAdapter


class FirstPipeline(object):
    fp =None
    #重寫父類一個方法:該方法只在開始爬蟲的時候被呼叫一次
    def open_spider(self,spider):
        print("開始爬蟲..............")
        self.fp=open('./test123.txt','w',encoding='utf-8')

    #專門用來處理item型別物件
    #可以接受爬蟲檔案體積過來的item物件
    #該方法每接受一個item物件就會被呼叫一次
    def process_item(self, item, spider):
        title=item['title']
        content=item['content']
        self.fp.write(title+":"+content+'\n')

        return item

    #繼續重寫父類方法
    def close_spider(self,spider):
        print('結束爬蟲列印................')
        self.fp.close()

在配置檔案settings.py中開啟管道

ITEM_PIPELINES = {
    #300表示執行優先順序,數值越小,優先順序越高
   'first.pipelines.FirstPipeline': 300,
}

1.4.3 多渠道儲存

如果要把爬取的資料分別存在本地和資料庫中,那麼就在pipelines.py中寫多個管道操作類
pipelines.py檔案中增加如下類

class MysqlPipeline(object):
    conn =None
    curcos=None
    #重寫父類一個方法:該方法只在開始爬蟲的時候被呼叫一次
    def open_spider(self,spider):
        print("mysql開始爬蟲..............")
        self.conn=pymysql.Connect(host='127.0.0.1',port=3306,user='root',password='root',db='test',charset='utf-8')

    #專門用來處理item型別物件
    #可以接受爬蟲檔案體積過來的item物件
    #該方法每接受一個item物件就會被呼叫一次
    def process_item(self, item, spider):
        title=item['title']
        content=item['content']
        self.curcos=self.conn.cursor()
        self.curcos.execute('insert into test values("%s","%s")%(item["author"],item["content"]) ')
        self.curcos.commit()
        # 會把item傳遞給下一個即將被執行的管道類
        return item

    #繼續重寫父類方法
    def close_spider(self,spider):
        print('結束mysql爬蟲...............')
        self.curcos.close()
        self.conn.close()

修改settings.py檔案

ITEM_PIPELINES = {
    #300表示執行優先順序,數值越小,優先順序越高
   'first.pipelines.FirstPipeline': 300,
   'first.pipelines.MysqlPipeline': 301,
}

1.5 全站資料爬取

全站資料爬取就是將網站中某個板塊下的全部頁碼對應的頁碼資料進行爬取
實現方式:

  • 將所有頁面url新增到start_urls列表中
  • 自行手動請求,需要手動傳送請求:yield scrapy.Request(url,callback)其中的callback是專門用於資料解析的

具體操作在爬蟲檔案中如下:

class XiaohuaSpider(scrapy.Spider):
    name='xiaohua'
    start_urls=['http://test.com/meinvxiaohua/']
	url='http://test.com/meinvxiaohua/?query=python&page=%d.html'
    def parse(self, response):
        li_list=response.xpath('//*[@id="content"]/div[2]')
        for li in li_list:
            img_name=li.xpath('./a[2]/b/text()').extract_first()

        if self.page_num<=3:#爬取前三頁
            new_url=format(self.url%self.page_num)#替換新頁面連線
            self.page_num+=1
            #手動傳送請求:callback回撥函式是專門用作資料解析
            yield scrapy.Request(url=new_url,callback=self.parse)#傳送新的連線,同時還是使用parse方法進行解析