一.什么是Srcapy?
Srcapy是為了爬取網站數據,提取結構性數據而編寫的應用框架,非常出名,非常強悍.他就是一個已經被集成各種功能包括高性能異步下載,隊列,分布式,解析,持久化等的強大通用性項目模板(超級武器霸王).主要學習它的特性,各個功能用法.
二.安裝
Linux:pip3 install scrapy
Windows:
1.pip3 install wheel
2.下載twisted http:
/
/
www.lfd.uci.edu
/
~gohlke
/
pythonlibs
/
#twisted
3.進入下載目錄,執行 pip3 install Twisted‑
17.1
.
0
‑cp35‑cp35m‑win_amd64.whl
4.pip3 install pywin32
5.pip3 install scrapy
三.基礎使用
1.創建項目:scrapy startproject +項目名稱
項目結構:
project_name/ scrapy.cfg: project_name/ __init__.py items.py pipelines.py settings.py spiders/ __init__.py scrapy.cfg 項目的主配置信息。(真正爬蟲相關的配置信息在settings.py文件中) items.py # 設置數據存儲模板,用於結構化數據,如:Django的Model pipelines # 數據持久化處理 settings.py # 配置文件,如:遞歸的層數、並發數,延遲下載等 spiders # 爬蟲目錄,如:創建文件,編寫爬蟲解析規則
2.創建爬蟲應用程序:
cd project_name #進入項目目錄 scrapy genspider # 應用名稱 爬取網頁的起始url (例如:scrapy genspider qiubai www.qiushibaike.com)
3.編寫爬蟲文件:在步驟2執行完畢后,會在項目的spiders中生成一個應用名的爬蟲文件
import scrapy class QiubaiSpider(scrapy.Spider): name = 'qiubai' #應用名稱 #允許爬取的域名(如果遇到非該域名的url則爬取不到數據) allowed_domains = ['https://www.qiushibaike.com/'] #起始爬取的url start_urls = ['https://www.qiushibaike.com/'] #訪問起始URL並獲取結果后的回調函數,該函數的response參數就是向起始的url發送請求后,獲取的響應對象.該函數返回值必須為可迭代對象或者NUll def parse(self, response): pass
4.設置修改settings.py配置文件配置
添加自己電腦的用戶代理信息;
將遵守robts協議改為False.
5.執行爬蟲程序:scrapy crawl 應用名稱,該種執行形式會顯示執行的日志信息
scrapy crawl 應用名稱 --nolog:執行時不返回日志信息
愛之初體驗:
# -*- coding: utf-8 -*- import scrapy class QiubaiSpider(scrapy.Spider): name = 'qiubai' # allowed_domains = ['www.qiushibaike.com'] start_urls = ['http://www.qiushibaike.com/'] def parse(self, response): info_list = [] li_list = response.xpath('//*[@class="recmd-right"]') for li in li_list: content = li.xpath('./a/text()')[0].extract() author = li.xpath('./div/a/span/text()').extract_first() info_dic = {'content':content, 'author':author} print(info_dic) info_list.append(info_dic) return info_list
四.scrapy的持久化存儲
1.基於終端指令的持久化存儲
保證爬蟲文件的parse方法中有可迭代對象列表或字典的返回,該返回值可以通過終端指令的形式指定格式的文件中進行持久化操作.但是只可以存儲一下格式文件:
執行輸出指定格式進行存儲
scrapy crawl 爬蟲名稱 -o xxx.json scrapy crawl 爬蟲名稱 -o xxx.xml scrapy crawl 爬蟲名稱 -o xxx.csv
2.基於管道的持久化存儲
scrapy框架中已經封裝好了高效便捷的持久化操作功能,我們直接使用即可.使用前先了解以下文件:items.py: 數據結構模板文件,定義數據屬性
pipelines.py: 管道文件,接受數據(items),進行持久化操作 持久化流程:
1.數據解析 2.爬蟲文件爬去到數據后,需要將數據封裝到items對象中. 3.使用yield關鍵字將items對象提交給pipelines管道進行持久化. 4.在管道文件中的process_item方法中接受爬蟲文件提交過來的items對象,然后編寫持久化存儲的代碼將item對象中存儲的數據進行持久化存儲. 5.settings.py配置文件中開啟管道
愛之初體驗:持久化存儲
爬蟲文件:qiubai.py
# -*- coding: utf-8 -*- import scrapy from papa.items import PapaItem class QiubaiSpider(scrapy.Spider): name = 'qiubai' # allowed_domains = ['www.qiushibaike.com'] start_urls = ['http://www.qiushibaike.com/'] def parse(self, response): li_list = response.xpath('//*[@class="recmd-right"]') for li in li_list: content = li.xpath('./a/text()')[0].extract() author = li.xpath('./div/a/span/text()').extract_first() item = PapaItem() item['content'] = content item['author'] = author yield item
items文件:items.py
import scrapy class PapaItem(scrapy.Item): # define the fields for your item here like: # name = scrapy.Field() author = scrapy.Field() content = scrapy.Field()
管道文件:pipelines.py
class PapaPipeline(object): #構造方法 def __init__(self): self.f = None #定義一個文件描述屬性 #下列都是在重寫父類的方法: #開始爬蟲時,執行一次 def open_spider(self,spider): print('開始爬') self.f = open('./data.txt','w') #因為該方法會被執行調用很多次,所以文件的開啟和關閉操作寫在了另外兩個各自執行一次的方法中 def process_item(self, item, spider): #將爬蟲程序提交的item進行持久化存儲 self.f.write(item['author'] + ':' + item['content'] + '\n') return item #結束爬蟲時,關閉一次文件 def close_spider(self,spider): self.f.close() print('結束爬')
配置文件:settings.py
ITEM_PIPELINES = { 'papa.pipelines.PapaPipeline': 300, #300表示為優先級,值越小優先級越高 }
為愛鼓掌:基於redis的管道存儲,在上述案例中,在管道文件里將item對象中的數據存儲到磁盤中,如果將上述案例中的item數據寫入redis數據庫的話,只需要將上述的管道文件修改成如下形式:
pipelines.py文件
from redis import Redis class PapaPipeline(object): conn = None def open_spider(self,spider): print('開始爬') #創建連接對象 self.conn = Redis(host='127.0.0.1',port=6379) def process_item(self, item, spider): #將爬蟲程序提交的item進行持久化存儲 dict = { 'author':item['author'], 'content':item['content'] } self.conn.lpush('data',dict) return item
五.scrapy的post請求
默認情況下,scrapy采用的是get請求,如果想發起post請求,則需要重寫start_requests方法,使其發起post請求.
import scrapy class BbbSpider(scrapy.Spider): name = 'bbb' # allowed_domains = ['www.baidu.com'] start_urls = ['https://baike.baidu.com/sug/'] def start_requests(self): #post請求參數 data = { 'kw':'dog', } for url in self.start_urls: yield scrapy.FormRequest(url=url,formdata=data,callback=self.parse) def parse(self, response): print(response.text)
六.scrapy的請求傳參
在平時的情況下,我們只爬取特定的一個頁面並不能滿足我們的需求,比如在電影網站中不僅要看簡介,還要爬取其二級網頁中的詳情信息,這時我們就需要用到傳參.
spider文件
# -*- coding: utf-8 -*- import scrapy from zhaogongzuo.items import ZhaogongzuoItem class A51jobSpider(scrapy.Spider): name = '51job' #allowed_domains = ['www.baidu.com'] start_urls = ['https://search.51job.com/list/010000,000000,0000,00,9,99,%25E7%2588%25AC%25E8%2599%25AB,2,1.html?lang=c&stype=1&postchannel=0000&workyear=99&cotype=99°reefrom=99&jobterm=99&companysize=99&lonlat=0%2C0&radius=-1&ord_field=0&confirmdate=9&fromType=4&dibiaoid=0&address=&line=&specialarea=00&from=&welfare='] def parse(self, response): div_list = response.xpath('//div[@class="el"]') for div in div_list: item = ZhaogongzuoItem() item['job'] = div.xpath('./p/span/a/text()').extract_first() item['detail_url'] = div.xpath('./p/span/a/@href').extract_first() item['company'] = div.xpath('./span[@class="t2"]/a/text()').extract_first() item['salary'] = div.xpath('./span[@class="t4"]/text()').extract_first() print(item) #meta參數:請求參數.meta字典就會傳遞給回調函數的response yield scrapy.Request(url=item['detail_url'], callback=self.parse_detail, meta={'item': item}) def parse_detail(self,response): #response.meta 返回接受到的meta字典 item = response.meta['item'] item['detail'] = response.xpath('/html/body/div[3]/div[2]/div[3]/div[1]/div/text()').extract_first() print(123) yield item
items文件
import scrapy class ZhaogongzuoItem(scrapy.Item): # define the fields for your item here like: # name = scrapy.Field() job = scrapy.Field() company = scrapy.Field() salary = scrapy.Field() detail = scrapy.Field() detail_url = scrapy.Field()
pipielines文件
import json class ZhaogongzuoPipeline(object): def __init__(self): self.f = open('data.txt','w') def process_item(self, item, spider): dic = dict(item) print(dic) json.dump(dic,self.f,ensure_ascii=False) return item def close_spider(self,spider): self.f.close()
settings文件更改的還是那些......
七.五大核心組件工作流程
引擎(Scrapy):用來處理整個系統的數據流處理,出發事務(框架核心)
調度器(Scheduler):用來接受引擎發過來的請求,壓入隊列中,並在引擎再次請求的時候返回,可以想象成一個url的優先隊列,由它來決定下一個要抓取的網址是什么,同時去除重復的網址.
下載器(Downloader):是所有組件中負擔最大的,它用於高速地下載網絡上的資源。Scrapy的下載器代碼不會太復雜,但效率高,主要的原因是Scrapy下載器是建立在twisted這個高效的異步模型上的(其實整個框架都在建立在這個模型上的)。
爬蟲(Spiders):用戶定制自己的爬蟲,用於從特定的網頁中提取自己需要的信息,即所謂的實體(Item)。用戶也可以從中提取出鏈接,讓Scrapy繼續抓取下一個頁面。
項目管道(Pipeline):負責處理爬蟲從網頁中抽取的實體,主要的功能是持久化實體,驗證實體的有效性,清除不需要的信息.當頁面被爬蟲解析后,將被發送到項目管道,並經過幾個特定的次序處理數據.
八.scrapy的日志等級
日志:就是在終端運行項目時,在小黑框中打印輸出信息.
日志信息的種類:
WARNING:警告;
INFO:一般的信息;
ERROR:一般錯誤;
DEBUG:調式信息
設置使用日志信息:
在settings.py配置文件中加入:LOG_LEVEL= '指定信息種類'
LOG_FILE = 'log.txt' #表示將日志信息寫入log.txt文件中
九.scrapy中selenium的使用
在使用scrapy框架爬取某些網站時,會碰見數據懶加載的的情況.結合selenium創建瀏覽器對象,通過此瀏覽器發送請求會迎刃而解.
原理:當引擎將網站url對應的請求提交給下載器后,下載器進行網頁數據的下載,然后將下載到的頁面數據封裝到response,提交給引擎,引擎將response再轉交給spiders.spiders接受到的response對象中存儲的頁面數據是沒有動態加載的數據的.要想獲取動態數據,則需要在下載中間件對下載器提交給引擎的response響應對象進行攔截,且對其內部存儲的頁面數據進行篡改,修改成攜帶了動態加載出的新聞數據,燃火將被篡改的response對象最終交給spiders進行解析操作.
使用流程:
重寫爬蟲文件的構造方法,在該方法中使用selenium實例化一個瀏覽器對象(瀏覽器對象只需實例化一次);
重寫爬蟲文件的closed(self,spider)方法,在其內部關閉瀏覽器對象.該方法是在爬蟲結束時被調用;
在下載中間件類的process_response方法中接收spider中的瀏覽器對象;
處理執行相關自動化操作(發起請求,獲取頁面數據);
實例化一個新的響應對象(from scrapy.http import HtmlResponse),且將頁面數據存儲到該對象中;
返回新的響應對象;
在配置文件中開啟下載中間件.
spider文件
import scrapy from selenium import webdriver class WangyiSpider(scrapy.Spider): name = 'wangyi' # allowed_domains = ['www.xxx.com'] start_urls = ['http://news.163.com/air/'] def __init__(self): self.drv = webdriver.Chrome(executable_path=r'C:\Users\Air\Anaconda3\爬蟲\chromedriver.exe') def parse(self, response): div_list = response.xpath('//div[@class="data_row news_article clearfix "]') for div in div_list: title = div.xpath('.//div[@class="news_title"]/h3/a/text()').extract_first() print(title) def closed(self,spider): print('關閉瀏覽器對象!') self.drv.quit()
middlewas文件
from scrapy.http import HtmlResponse from time import sleep def process_response(self, request, response, spider): # Called with the response returned from the downloader. # Must either; # - return a Response object # - return a Request object # - or raise IgnoreRequest print('即將返回一個新的響應對象') drv = spider.drv drv.get(url=request.url) sleep(3) #包含了動態加載出來的新聞數據 page_text = drv.page_source sleep(3) return HtmlResponse(url=spider.drv.current_url,body=page_text,encoding='utf-8',request=request)
十.如何增加爬蟲效率
增加並發:默認scrapy開啟的並發線程為32個,可以適當進行增加.在settings配置文件中修改CONCURRENT_REQUESTS = 100,這樣就設置成100啦. 降低日志級別:在運行scrapy時,會有大量日志信息的輸出,為了減少cpu的使用頻率,可以設置log輸出信息為INFO或者ERROR即可.在配置文件中編寫:LOG_LEVEL= 'INFO'. 禁止cookie:如果不是真的需要cookie,則在scrapy爬取數據時可以禁止cookie從而減少cpu的使用率,提升爬取效率.在配置文件中編寫:COOKIES_ENABLED = False. 禁止重試:對失敗的HTTP進行重試新請求(重試)會減慢爬取速度,因此可以禁止重試.在settings文件中編寫:RETRY_ENABLED = False. 減少下載超時:如果對一個非常慢的鏈接進行爬取,減少下載超時可以能讓卡主的鏈接快速被放棄,從而提升效率.在配置文件中編寫:DOWNLOAD_TIMEOUT = 10,超過時間為10s.
十一.分布式爬蟲
原生的的scrapy框架不可以實現分布式:因為調度器不能被共享;管道無法被共享
使用到的scrapy-redis組件作用:提供了可以被共享的調度器和管道.
分布式爬蟲實現流程:
1.環境安裝:pip install scrapy-redis 2.創建工程 3.創建爬蟲文件:RedisCrawlSpider RedisSpider -scrapy genspider -t crawl xxx www.xxx.com 4.對爬蟲文件中的相關屬性進行修改: 導包:from scrapy_redis.spiders import RedisCrawlSpider 將當前爬蟲文件的父類設置成RedisCrawlSpider 將起始url列表替換成redis_key = 'xxx' (調度器隊列的名稱) 5.在配置文件中進行配置 使用組件中封裝好的可以被共享的管道類: ITEM_PIPELINES = { 'scrapy_redis.pipelines.RedisPipeline':400 } 配置調度器(使用組件中封裝好的可以被共享的調度器): #增加了一個去重容器類的配置,作用使用Redis的set集合來儲存請求的指紋數據,從而實現請求去重的持久化. DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFDupeFilter" #使用scrapy-redis組件自己的調度器 SCHEDULER = "scrapy_redis.scheduler.Scheduler" #配置調度器是否要持久化,也就是當爬蟲結束了要不要清空redis中請求列隊和去重指紋的set.如果是True,就表示需要持久化存儲,就不清空數據,否則清空數據 SCHEDULER_PERSITE= True #指定存儲數據的redis REDIS_HOST = 'redis服務的ip地址' REDIS_POST = 6379 #配置redis數據庫的配置文件 -取消保護模式:protected-mode no -bind 綁定:#bind 127.0.0.1 -啟動redis
6.執行分布式程序
scrapy runspider xxx.py
7.向調度器隊列中寫入一個起始url:
在redis-cli中執行
十二.增量式爬蟲
什么是增量式爬蟲:通過爬蟲程序檢測某網站數據更新的情況,以變爬到該網站更新出的新數據.
如何進行:(1) 在發送請求之前判斷這個url是不是之前爬過
(2) 在解析內容后判斷這部分內容是不是之前爬過.
(3) 寫入存儲介質時判斷內容是不是已經在介質中存在.
分析:可以發現,其實增量爬取的核心就是去重,至於去重的操作在哪步起作用,只能說各有利弊.建議(1)(2)思路根據實際情況取一個(也可能都用).(1)思路適合不斷有新頁面出現的網站,比如說小說的新章節,每天的最新新聞等等;(2)思路則適合頁面內容會更新的網站;(3)思路相當於是最后一道防線,這樣做可以最大程度上達到去重的目的.
去重方法:
將爬取過程中產生的url進行存儲,存儲在redis中的set中.當下次進行數據爬取時,首先對即將發起的請求對應url在存儲的url的set中做判斷,如果存在則不進行請求,否則才請求;
對爬取到的網頁內容進行唯一標識的制定,然后將該唯一標識存儲至redis的set中,當下次爬到網頁數據的時候,在進行持久化存儲之前,首先可以先判斷該數據的唯一標識在redis的set中是否存在,在決定是否進行持久化存儲.
爬蟲文件:
from redis import Redis class IncrementproPipeline(object): conn = None def open_spider(self,spider): self.conn = Redis(host='127.0.0.1',port=6379) def process_item(self, item, spider): dic = { 'name':item['name'], 'kind':item['kind'] } print(dic) self.conn.lpush('movieData',dic) return item - 需求:爬取糗事百科中的段子和作者數據。 爬蟲文件: # -*- coding: utf-8 -*- import scrapy from scrapy.linkextractors import LinkExtractor from scrapy.spiders import CrawlSpider, Rule from incrementByDataPro.items import IncrementbydataproItem from redis import Redis import hashlib class QiubaiSpider(CrawlSpider): name = 'qiubai' # allowed_domains = ['www.xxx.com'] start_urls = ['https://www.qiushibaike.com/text/'] rules = ( Rule(LinkExtractor(allow=r'/text/page/\d+/'), callback='parse_item', follow=True), Rule(LinkExtractor(allow=r'/text/$'), callback='parse_item', follow=True), ) #創建redis鏈接對象 conn = Redis(host='127.0.0.1',port=6379) def parse_item(self, response): div_list = response.xpath('//div[@id="content-left"]/div') for div in div_list: item = IncrementbydataproItem() item['author'] = div.xpath('./div[1]/a[2]/h2/text() | ./div[1]/span[2]/h2/text()').extract_first() item['content'] = div.xpath('.//div[@class="content"]/span/text()').extract_first() #將解析到的數據值生成一個唯一的標識進行redis存儲 source = item['author']+item['content'] source_id = hashlib.sha256(source.encode()).hexdigest() #將解析內容的唯一表示存儲到redis的data_id中 ex = self.conn.sadd('data_id',source_id) if ex == 1: print('該條數據沒有爬取過,可以爬取......') yield item else: print('該條數據已經爬取過了,不需要再次爬取了!!!')
管道文件:
from redis import Redis class IncrementbydataproPipeline(object): conn = None def open_spider(self, spider): self.conn = Redis(host='127.0.0.1', port=6379) def process_item(self, item, spider): dic = { 'author': item['author'], 'content': item['content'] } # print(dic) self.conn.lpush('qiubaiData', dic) return item