目錄
一、概述
Scrapy,Python開發的一個快速、高層次的屏幕抓取和web抓取框架,用於抓取web站點並從頁面中提取結構化的數據。Scrapy用途廣泛,可以用於數據挖掘、監測和自動化測試.
其最初是爲了頁面抓取 (更確切來說, 網絡抓取 )所設計的, 後臺也應用在獲取API所返回的數據(例如 Amazon Associates Web Services ) 或者通用的網絡爬蟲.
Scrapy吸引人的地方在於它是一個框架,任何人都可以根據需求方便的修改。它也提供了多種類型爬蟲的基類,如BaseSpider、sitemap爬蟲等,最新版本又提供了web2.0爬蟲的支持.
二、Scrapy五大基本構成:
Scrapy框架主要由五大組件組成,它們分別是調度器(Scheduler)、下載器(Downloader)、爬蟲(Spider)和實體管道(Item Pipeline)、Scrapy引擎(Scrapy Engine)。下面我們分別介紹各個組件的作用。
(1)、調度器(Scheduler):
調度器,說白了把它假設成爲一個URL(抓取網頁的網址或者說是鏈接)的優先隊列,由它來決定下一個要抓取的網址是 什麼,同時去除重複的網址(不做無用功)。用戶可以自己的需求定製調度器。
(2)、下載器(Downloader):
下載器,是所有組件中負擔最大的,它用於高速地下載網絡上的資源。Scrapy的下載器代碼不會太複雜,但效率高,主要的原因是Scrapy下載器是建立在twisted這個高效的異步模型上的(其實整個框架都在建立在這個模型上的)。
(3)、 爬蟲(Spider):
爬蟲,是用戶最關心的部份。用戶定製自己的爬蟲(通過定製正則表達式等語法),用於從特定的網頁中提取自己需要的信息,即所謂的實體(Item)。 用戶也可以從中提取出鏈接,讓Scrapy繼續抓取下一個頁面。
(4)、 實體管道(Item Pipeline):
實體管道,用於處理爬蟲(spider)提取的實體。主要的功能是持久化實體、驗證實體的有效性、清除不需要的信息。
(5)、Scrapy引擎(Scrapy Engine):
Scrapy引擎是整個框架的核心.它用來控制調試器、下載器、爬蟲。實際上,引擎相當於計算機的CPU,它控制着整個流程。
三、整體架構圖
本圖按順序說明整個程序執行時候發生的順序。
注意在調用下載器時,往往有一個下載器中間件,使下載速度提速。
官網架構圖
四、Scrapy安裝以及生成項目
新建一個項目,該項目的結構如下:
執行命令,widows和ubuntu命令格式是一樣的:
下載方式
ubuntu,打開一個終端,輸入pip install scrapy(或pip3 install scrapy)
widows ,打開一個cmd,輸入pip install scrapy,前提是你裝了pip
scrapy startproject 項目名
scrapy genspider 爬蟲名 域名
scrapy crawl 爬蟲名
我使用的是widows版本,下面演示創建項目的例子
打開cmd,輸入(默認是在C:\Users\Administrator>這個目錄下,你可以自行切換)
scrapy startproject myfirstPj
cd my firstPj
scrapy genspider baidu www.baidu.com
創建後目錄大致頁如下
|-ProjectName #項目文件夾
|-ProjectName #項目目錄
|-items.py #定義數據結構
|-middlewares.py #中間件
|-pipelines.py #數據處理
|-settings.py #全局配置
|-spiders
|-__init__.py #爬蟲文件
|-baidu.py
|-scrapy.cfg #項目基本配置文件
spiders下的baidu.py是scrapy自動爲我們生成的
下面再看一下spdier項目的配置文件,打開文件settings.py
BOT_NAME:項目名
USER_AGENT:默認是註釋的,這個東西非常重要,如果不寫很容易被判斷爲電腦,簡單點洗一個Mozilla/5.0即可
ROBOTSTXT_OBEY:是否遵循機器人協議,默認是true,需要改爲false,否則很多東西爬不了
CONCURRENT_REQUESTS:最大併發數,很好理解,就是同時允許開啓多少個爬蟲線程
DOWNLOAD_DELAY:下載延遲時間,單位是秒,控制爬蟲爬取的頻率,根據你的項目調整,不要太快也不要太慢,默認是3秒,即爬一個停3秒,設置爲1秒性價比較高,如果要爬取的文件較多,寫零點幾秒也行
COOKIES_ENABLED:是否保存COOKIES,默認關閉,開機可以記錄爬取過程中的COKIE,非常好用的一個參數
DEFAULT_REQUEST_HEADERS:默認請求頭,上面寫了一個USER_AGENT,其實這個東西就是放在請求頭裏面的,這個東西可以根據你爬取的內容做相應設置。
ITEM_PIPELINES:項目管道,300爲優先級,越低越爬取的優先度越高
比如我的pipelines.py裏面寫了兩個管道,一個爬取網頁的管道,一個存數據庫的管道,我調整了他們的優先級,如果有爬蟲數據,優先執行存庫操作。
ITEM_PIPELINES = { 'scrapyP1.pipelines.BaiduPipeline': 300, 'scrapyP1.pipelines.BaiduMysqlPipeline': 200, }
到這裏我們嘗試用scrapy做一下爬取,打開spider.py下的baidu.py(取決於你scrapy genspider 爬蟲名 域名時輸入的爬蟲名)
輸入一下代碼,我們使用xpath提取百度首頁的標題title
import scrapy
class BaiduSpider(scrapy.Spider):
name = 'baidu'
allowed_domains = ['www.baidu.com']
start_urls = ['http://www.baidu.com/']
def parse(self, response):
tile=response.xpath('//html/head/title/text()')
print(tile)
打開一個終端cmd,輸入scrapy crawl baidu(爬蟲名),就可以看到一大堆輸出信息,而其中就包括我們要的內容
使用終端運行太麻煩了,而且不能提取數據,我們一個寫一個run文件作爲程序的入口,splite是必須寫的,目的是把字符串轉爲列表形式,第一個參數是scrapy,第二個crawl,第三個baidu
from scrapy import cmdline
cmdline.execute('scrapy crawl baidu'.split())
可以在編輯器中輸出了
五、日誌等級與日誌保存
在setting.py裏面可以設置日誌的等級與日誌存放的路徑
相關變量
LOG_LEVEL= ""
LOG_FILE="日誌名.log"
日誌等級分爲
1.DEBUG 調試信息
2.INFO 一般信息
3.WARNING 警告
4.ERROR 普通錯誤
5.CRITICAL 嚴重錯誤
如果設置
LOG_LEVEL="WARNING",就只會WARNING等級之下的ERROR和CRITICAL
默認等級是1
六、導出爲json或scv格式
執行爬蟲文件時添加-o選項即可
scrapy crawl 項目名 -o *.csv
scrapy crawl 項目名 -o *.json
對於json文件,在setting.js文件裏添加,設置編碼格式,否則會亂碼:
FEED_EXPORT_ENCODING='utf-8'
示例:
from scrapy import cmdline
cmdline.execute('scrapy crawl baidu -o baidu.csv'.split())
七、一個完整的案例
這個項目我們的主題是爬騰訊視頻的電影信息,包括電影名和描述
1.創建項目
打開一個終端輸入(建議放到合適的路徑下,默認是C盤)
scrapy startproject TXmovies
cd TXmovies
scrapy genspider txms v.qq.com
2.修改setting
修改三項內容,第一個是不遵循機器人協議,第二個是下載間隙,由於下面的程序要下載多個頁面,所以需要給一個間隙(不給也可以,只是很容易被偵測到),第三個是請求頭,添加一個User-Agent,第四個是打開一個管道
ROBOTSTXT_OBEY = False DOWNLOAD_DELAY = 1 DEFAULT_REQUEST_HEADERS = { 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'Accept-Language': 'en', 'User-Agent':'Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.94 Safari/537.36' } ITEM_PIPELINES = { 'TXmovies.pipelines.TxmoviesPipeline': 300, }
3.確認要提取的數據,item項
item定義你要提取的內容(定義數據結構),比如我提取的內容爲電影名和電影描述,我就創建兩個變量。Field方法實際上的做法是創建一個字典,給字典添加一個建,暫時不賦值,等待提取數據後再賦值。下面item的結構可以表示爲:{'name':'','descripition':''}。
# -*- coding: utf-8 -*-
# Define here the models for your scraped items
#
# See documentation in:
# https://docs.scrapy.org/en/latest/topics/items.html
import scrapy
class TxmoviesItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
name = scrapy.Field()
description = scrapy.Field()
4.寫爬蟲程序
我們要寫的部分是parse方法裏的內容,重點在於如何寫xpath,關於xpath我不多講,有興趣可以看看我另一篇文章,XPATH教程
引入剛剛寫好的item,剛剛說了item裏面創建的變量就是字典的鍵值,可以直接進行賦值。賦值後交給管道處理。
簡單講一下這一段代碼的思路,首先騰訊視頻的url爲https://v.qq.com/x/bu/pagesheet/list?append=1&channel=cartoon&iarea=1&listpage=2&offset=0&pagesize=30
我們注意到offset這一項,第一頁的offset爲0,第二頁爲30,依次推列。在程序中這一項用於控制抓取第一頁,但是也要給一個範圍,不可能無限大,否則會報錯,可以去看看騰訊一共有多少頁視頻,也可以寫一個異常捕獲機制,捕捉到請求出錯則退出。我這裏僅僅是示範,所以只給了120,也就是4頁。
yield
程序裏一共有兩個yield,我比較喜歡叫它中斷,當然中斷只在CPU中發生,它的作用是移交控制權,在本程序中,我們對item封裝數據後,就調用yield把控制權給管道,管道拿到處理後return返回,又回到該程序。這是對第一個yield的解釋。
第二個yield稍微複雜點,這條程序裏利用了一個回調機制,即callback,回調的對象是parse,也就是當前方法,通過不斷的回調,程序將陷入循環,如果不給程序加條件,就會陷入死循環,如本程序我把if去掉,那就是死循環了。
yield scrapy.Request(url=url,callback=self.parse)
xpath
還有一個要注意的是如何提取xpathl裏的數據,我們的寫法有四種,第一種寫法拿到selector選擇器,也就是原數據,裏面有一些我們用不到的東西。第二個extract(),將選擇器序列號爲字符串。第三個和第四個一樣,拿到字符串裏的第一個數據,也就是我們要的數據。
items['name']=i.xpath('./a/@title')[0]
items['name']=i.xpath('./a/@title').extract()
items['name']=i.xpath('./a/@title').extract_first()
items['name']=i.xpath('./a/@title').get()
# -*- coding: utf-8 -*-
import scrapy
from ..items import TxmoviesItem
class TxmsSpider(scrapy.Spider):
name = 'txms'
allowed_domains = ['v.qq.com']
start_urls = ['https://v.qq.com/x/bu/pagesheet/list?append=1&channel=cartoon&iarea=1&listpage=2&offset=0&pagesize=30']
offset=0
def parse(self, response):
items=TxmoviesItem()
lists=response.xpath('//div[@class="list_item"]')
for i in lists:
items['name']=i.xpath('./a/@title').get()
items['description']=i.xpath('./div/div/@title').get()
yield items
if self.offset < 120:
self.offset += 30
url = 'https://v.qq.com/x/bu/pagesheet/list?append=1&channel=cartoon&iarea=1&listpage=2&offset={}&pagesize=30'.format(
str(self.offset))
yield scrapy.Request(url=url,callback=self.parse)
5.交給管道輸出
管道可以處理提取的數據,如存數據庫。我們這裏僅輸出。
# -*- coding: utf-8 -*-
# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html
class TxmoviesPipeline(object):
def process_item(self, item, spider):
print(item)
return item
6.run,執行項目
from scrapy import cmdline
cmdline.execute('scrapy crawl txms'.split())
7.測試結果
白色的管道輸出的結果,紅色的調試信息
8.流程梳理
新建項目-》進入項目-》新建爬蟲文件-》明確抓取的內容,寫item-》寫爬蟲程序,爬取數據-》交給管道處理數據-》調整全局配置setting-》執行爬蟲程序,可以通過終端或者在程序裏寫一個run程序
9.提速:多線程爬取
如果你實現了上面的實驗,不難發現其爬取速度是非常慢,根本的原因就是因爲它是順序執行的,你可以從結果中看出,總是前面一頁的內容被輸出,再輸出後面的內容。不適合處理數據量較大的情況,一個好的方式是採用多線程的方法,這裏的多線程是基於方法的多線程,並不是通過創建Thread對象來實現,是在一個方法中,一次性把請求交給調度器。
我們通過重寫start_requests方法來實現我們的想法(這個方法的源碼在__init__.py下面,有興趣可以看一下)
# -*- coding: utf-8 -*-
import scrapy
from ..items import TxmoviesItem
class TxmsSpider(scrapy.Spider):
name = 'txms'
allowed_domains = ['v.qq.com']
url='https://v.qq.com/x/bu/pagesheet/list?append=1&channel=cartoon&iarea=1&listpage=2&offset={}&pagesize=30'
offset=0
def start_requests(self):
for i in range(0,121,30):
url=self.url.format(i)
yield scrapy.Request(
url=url,
callback=self.parse
)
def parse(self, response):
items=TxmoviesItem()
lists=response.xpath('//div[@class="list_item"]')
for i in lists:
items['name']=i.xpath('./a/@title').get()
items['description']=i.xpath('./div/div/@title').get()
yield items