一、Scrapy框架簡介
Scrapy是:由Python/ target=_blank class=infotextkey>Python語言開發的一個快速、高層次的屏幕抓取和web抓取框架,用于抓取web站點并從頁面中提取結構化的數據,只需要實現少量的代碼,就能夠快速的抓取。
Scrapy使用了Twisted異步網絡框架來處理網絡通信,可以加快我們的下載速度,不用自己去實現異步框架,并且包含了各種中間件接口,可以靈活地實現各種需求。
Scrapy可以應用在包括數據挖掘、信息處理或存儲歷史數據等一系列的程序中,其最初是為頁面抓取(更確切地說是網絡抓取)而設計的,也可以應用于獲取API所返回的數據(例如Amazon Associates Web Services)或者通用的網絡爬蟲。
二、Scrapy架構
1、架構圖
官方架構圖
翻譯架構圖
2、組件
Scrapy主要包括了以下組件:
- 爬蟲中間件(Spider Middleware):位于Scrapy引擎和爬蟲之間的框架,主要用于處理爬蟲的響應輸入和請求輸出。
- 調度器中間件(Scheduler Middleware):位于Scrapy引擎和調度器之間的框架,主要用于處理從Scrapy引擎發送到調度器的請求和響應。
- 調度器(Scheduler):用來接收引擎發過來的請求,壓入隊列中,并在引擎再次請求的時候返回。它就像是一個URL的優先隊列,由它來決定下一個要抓取的網址是什么,同時在這里會去除重復的網址。
- 下載器中間件(Downloader Middleware):位于Scrapy引擎和下載器之間的框架,主要用于處理Scrapy引擎與下載器之間的請求及響應。代理IP和用戶代理可以在這里設置。
- 下載器(Downloader):用于下載網頁內容,并將網頁內容返回給爬蟲。
Scrapy引擎(ScrapyEngine):用來控制整個系統的數據處理流程,并進行事務處理的觸發。
- 爬蟲(Spiders):爬蟲主要是干活的,用于從特定網頁中提取自己需要的信息,即所謂的項目(又稱實體)。也可以從中提取URL,讓Scrapy繼續爬取下一個頁面。
- 項目管道(Pipeline):負責處理爬蟲從網頁中爬取的項目,主要的功能就是持久化項目、驗證項目的有效性、清除不需要的信息。當頁面被爬蟲解析后,將被送到項目管道,并經過幾個特定的次序來處理其數據。
3、運行流程
數據流(Data flow),Scrapy中的數據流由執行引擎(ScrapyEngine)控制,其過程如下:
- 引擎打開一個網站(open a domain),找到處理該網站的Spider并向該spider請求第一個要爬取的URL(s)。
- 引擎從Spider中獲取到第一個要爬取的URL并在調度器(Scheduler)以Request調度。
- 引擎向調度器請求下一個要爬取的URL。
- 調度器返回下一個要爬取的URL給引擎,引擎將URL通過下載中間件(請求(request)方向)轉發給下載器(Downloader)。
- 一旦頁面下載完畢,下載器生成一個該頁面的Response,并將其通過下載中間件(返回(response)方向)發送給引擎。
- 引擎從下載器中接收到Response并通過Spider中間件(輸入方向)發送給Spider處理。
- Spider處理Response并返回爬取到的Item及(跟進的)新的Request給引擎。
- 引擎將(Spider返回的)爬取到的Item給Item Pipeline,將(Spider返回的)Request給調度器。
- (從第二步)重復直到調度器中沒有更多地request,引擎關閉該網站
三、Scrapy安裝以及生成項目
1、下載安裝
linux下載方式,直接安裝
pip install scrapy
或者
pip3 install scrapy)
windows 如果用Pycharm的話,在Pycharm底部打開命令終端
輸入命令
pip install scrapy
2、創建Scrapy項目
#創建一個叫ScrapyDemmo
scrapy startproject ScrapyDemmo
#進入項目文件夾
cd ScrapyDemmo
#創建一個名為baidu的爬蟲,爬蟲目標www.baidu.com
scrapy genspider baidu www.baidu.com
創建完成后,目錄結構如下:
- scrapy.cfg: 項目的配置文件。
- scrapyspider/: 該項目的python模塊。之后您將在此加入代碼。
- scrapyspider/items.py: 項目中的item文件。
- scrapyspider/pipelines.py: 項目中的pipelines文件。
- scrapyspider/settings.py: 項目的設置文件。
- scrapyspider/spiders/: 放置spider代碼的目錄。
spiders下的baidu.py是scrapy用命令(scrapy genspider baidu www.baidu.com)自動為我們生成的。
內容如下:
import scrapy
class BaiduSpider(scrapy.Spider):
name = 'baidu'
allowed_domains = ['www.baidu.com']
start_urls = ['http://www.baidu.com/']
def parse(self, response):
title = response.xpath('//html/dead/title/text()')
print(title)
當然,可以不用命令生成,可以自己在spiders下創建爬蟲,您必須繼承 scrapy.Spider 類, 且定義以下三個屬性:
- name: 用于區別Spider。 該名字必須是唯一的,您不可以為不同的Spider設定相同的名字。
- start_urls: 包含了Spider在啟動時進行爬取的url列表。 因此,第一個被獲取到的頁面將是其中之一。 后續的URL則從初始的URL獲取到的數據中提取。
- parse() 是spider的一個方法。 被調用時,每個初始URL完成下載后生成的 Response 對象將會作為唯一的參數傳遞給該函數。 該方法負責解析返回的數據(response data),提取數據(生成item)以及生成需要進一步處理的URL的 Request 對象。
3、運行爬蟲
運行方法:
在項目目錄底下用命令運行,如下,我項目目錄 D:PythonScrapyDemmo,運行name為baidu的爬蟲
D:PythonScrapyDemmo> scrapy crawl baidu
在scrapy中,為了避免每一次運行或調試都輸入一串命令,可以在項目文件下新建一個run.py文件,每次運行爬蟲只需要運行此腳本即可。且運行調試模式也需要設置此啟動腳本。
from scrapy import cmdline
cmdline.execute("scrapy crawl baidu".split())
最后運行這個run.py即可,執行結果:
D:PythonvenvScriptspython.exe D:PythonScrapyDemmoScrapyDemmorun.py
2022-10-28 10:12:55 [scrapy.utils.log] INFO: Scrapy 2.7.0 started (bot: ScrapyDemmo)
2022-10-28 10:12:55 [scrapy.utils.log] INFO: Versions: lxml 4.9.1.0, libxml2 2.9.12, cssselect 1.1.0, parsel 1.6.0, w3lib 2.0.1, Twisted 22.8.0, Python 3.9.13 (tags/v3.9.13:6de2ca5, May 17 2022, 16:36:42) [MSC v.1929 64 bit (AMD64)], pyOpenSSL 22.1.0 (OpenSSL 3.0.5 5 Jul 2022), cryptography 38.0.1, Platform Windows-10-10.0.22000-SP0
2022-10-28 10:12:55 [scrapy.crawler] INFO: Overridden settings:
{'BOT_NAME': 'ScrapyDemmo',
'NEWSPIDER_MODULE': 'ScrapyDemmo.spiders',
'REQUEST_FINGERPRINTER_IMPLEMENTATION': '2.7',
'ROBOTSTXT_OBEY': True,
'SPIDER_MODULES': ['ScrapyDemmo.spiders'],
'TWISTED_REACTOR': 'twisted.inte.NET.asyncioreactor.AsyncIOSelectorReactor'}
2022-10-28 10:12:55 [asyncio] DEBUG: Using selector: SelectSelector
...
...
若嫌棄scrapy日志文件太雜亂,想無日志輸出,只需在后面增加--nolog即可:
from scrapy import cmdline
cmdline.execute('scrapy crawl baidu --nolog'.split())
執行導出為json或scv格式,執行爬蟲文件時添加-o選項即可
scrapy crawl 項目名 -o *.csv
scrapy crawl 項目名 -o *.json
對于json文件,在setting.js文件里添加,設置編碼格式,否則會亂碼:
from scrapy import cmdline
cmdline.execute('scrapy crawl baidu -o baidu.csv'.split())
四、Scrapy配置文件settings.py
默認配置文件,主要設置參數:
BOT_NAME = 'ScrapyDemmo' #Scrapy項目的名字,這將用來構造默認 User-Agent,同時也用來log,當您使用 startproject 命令創建項目時其也被自動賦值。
SPIDER_MODULES = ['ScrapyDemmo.spiders'] #Scrapy搜索spider的模塊列表 默認: [xxx.spiders]
NEWSPIDER_MODULE = 'ScrapyDemmo.spiders' #使用 genspider 命令創建新spider的模塊。默認: 'xxx.spiders'
#爬取的默認User-Agent,除非被覆蓋
#USER_AGENT = 'ScrapyDemmo (+http://www.yourdomain.com)'
#如果啟用,Scrapy將會采用 robots.txt策略
ROBOTSTXT_OBEY = True
#Scrapy downloader 并發請求(concurrent requests)的最大值,默認: 16
#CONCURRENT_REQUESTS = 32
#為同一網站的請求配置延遲(默認值:0)
# See https://docs.scrapy.org/en/latest/topics/settings.html#download-delay
# See also autothrottle settings and docs
#DOWNLOAD_DELAY = 3 #下載器在下載同一個網站下一個頁面前需要等待的時間,該選項可以用來限制爬取速度,減輕服務器壓力。同時也支持小數:0.25 以秒為單位
#下載延遲設置只有一個有效
#CONCURRENT_REQUESTS_PER_DOMAIN = 16 #對單個網站進行并發請求的最大值。
#CONCURRENT_REQUESTS_PER_IP = 16 #對單個IP進行并發請求的最大值。如果非0,則忽略
#禁用Cookie(默認情況下啟用)
#COOKIES_ENABLED = False
#禁用Telnet控制臺(默認啟用)
#TELNETCONSOLE_ENABLED = False
#覆蓋默認請求標頭:
#DEFAULT_REQUEST_HEADERS = {
# 'Accept': 'text/html,Application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
# 'Accept-Language': 'en',
#}
#項目管道,300為優先級,越低越爬取的優先度越高
# See https://docs.scrapy.org/en/latest/topics/item-pipeline.html
#ITEM_PIPELINES = {
# 'ScrapyDemmo.pipelines.ScrapydemmoPipeline': 300,
#}
還可以設置日志的等級與日志存放的路徑:
相關變量
LOG_LEVEL= ""
LOG_FILE="日志名.log"
日志等級分為,默認等級是1
- DEBUG 調試信息
- INFO 一般信息
- WARNING 警告
- ERROR 普通錯誤
- CRITICAL 嚴重錯誤
如果設置
LOG_LEVEL="WARNING",就只會WARNING等級之下的ERROR和CRITICAL
一般主要需要配置的幾個參數,其他按需配置即可。
USER_AGENT:默認是注釋的,這個東西非常重要,如果不寫很容易被判斷為電腦爬蟲。
ROBOTSTXT_OBEY:是否遵循機器人協議,默認是true,需要改為false,否則很多東西爬不了
DEFAULT_REQUEST_HEADERS:和USER_AGENT類似,只是參數更完整。
五、完整案例(下載圖片)
用scrapy框架下載以前的示例:python爬蟲之批量下載圖片
1、修改settings.py 主要參數
#關閉robot.txt協議
ROBOTSTXT_OBEY = False
#頁面延遲下載,我這里測試,可以先不設置
DOWNLOAD_DELAY = 1
# 是否啟用Cookie
COOKIES_ENABLED = True
#請求頭
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 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36'
}
#打開下載器
DOWNLOADER_MIDDLEWARES = {
'ScrapyDemmo.middlewares.ScrapydemmoDownloaderMiddleware': 543,
}
#打開優先級,并添加自己編寫的圖片下載管道
ITEM_PIPELINES = {
'ScrapyDemmo.pipelines.ScrapydemmoPipeline': 300,
'ScrapyDemmo.pipelines.ImageDownloadPipeline': 300,
}
#添加下載儲存目錄
IMAGES_STORE = 'D:Pythonpic'
# 文件保存時間
#IMAGES_EXPIRES = 90
2、定義Item字段(Items.py)
本項目用于下載圖片,因此可以僅構建圖片名和圖片地址字段。
import scrapy
class ScrapydemmoItem(scrapy.Item):
#圖片下載鏈接
image_url = scrapy.Field()
#圖片名稱
image_name = scrapy.Field()
3、編寫爬蟲文件(spiders目錄下)
這里文件名為:image_download.py
以前用requests庫和BeautifulSoup庫下載圖片,這里就不需要了,scrapy自帶相關函數和方法。
scrapy元素定位,提供三種方式,正則、Xpath表達式、css。
我這里有xpath定位方式。
import scrapy
import re
from ..items import ScrapydemmoItem
class ImageSpider(scrapy.Spider):
name = 'image_download'
allowed_domains = ['desk.3gbizhi.com']
start_urls = ['https://desk.3gbizhi.com/deskMV/index.html']
def parse(self, response):
#導入Items.py字段
items = ScrapydemmoItem()
#獲取所有鏈接列表
lists = response.xpath('//div[5]/ul/li')
#點位元素循環獲取圖片鏈接和圖片名稱
for i in lists:
#圖片名稱
image_name = i.xpath('./a/img/@alt').get()
#圖片鏈接
items['image_url'] = i.xpath('./a/img/@*[1]').get().replace('.278.154.jpg', '')
#圖片格式類型
image_type = re.sub(r'h.*d+.', '', items['image_url'])
#拼接文件名,圖片名稱+圖片格式
items['image_name'] = '{}.{}'.format(image_name, image_type)
yield items
#循環跳轉下一頁,并重復返回數據,這里測試先下載1頁的圖片,總共23頁。
for i in range(2,3):
next_url = 'https://desk.3gbizhi.com/deskMV/index_{}.html'.format(i)
yield scrapy.Request(next_url,callback=self.parse)
關于 yield 的理解,?先,如果你還沒有對yield有個初步分認識,那么你先把yield看做“return”,這個是直觀的,它?先是個return。
最主要的不同在于yield在返回值后還可以繼續運行接下來的代碼,使用的函數會返回一個生成器,而return在返回后就不在執行代碼。
以上兩個yield:
- yield items:這里我們通過 yield 返回的不是 Request 對象,而是一個 ScrapydemmoItem 對象。scrap有框架獲得這個對象之后,會將這個對象傳遞給 pipelines.py來做進一步處理。我們將在 pipelines.py里將傳遞過來的 scrapy.Item 對象保存到數據庫里去。
- yield scrapy.Request:這里是在爬取完一頁的信息后,我們在當前頁面獲取到了下一頁的鏈接,然后通過 yield 發起請求,并且將 parse 自己作為回調函數來處理下一頁的響應。
4、修改管道文件pipelines.py用于下載圖片
除了爬取文本,我們可能還需要下載文件、視頻、圖片、壓縮包等,這也是一些常見的需求。scrapy提供了FilesPipeline和ImagesPipeline,專門用于下載普通文件及圖片。
繼承 Scrapy 內置的 ImagesPipeline,只需要重寫get_media_requests 和item_completed函數即可。
from scrapy.pipelines.images import ImagesPipeline
from scrapy.exceptions import DropItem
from scrapy import Request
class ScrapydemmoPipeline:
def process_item(self, item, spider):
return item
class ImageDownloadPipeline(ImagesPipeline):
def get_media_requests(self, item, info):
# 下載圖片,如果傳過來的是集合需要循環下載
# meta里面的數據是從spider獲取,然后通過meta傳遞給下面方法:file_path
yield Request(url = item['image_url'],meta = {'filename':item['image_name']})
def item_completed(self, results, item, info):
# 分析下載結果并剔除下載失敗的圖片
image_paths = [x['path'] for ok, x in results if ok]
if not image_paths:
raise DropItem("Item contains no images")
return item
def file_path(self, request, response=None, info=None):
# 接收上面meta傳遞過來的圖片名稱
file_name = request.meta['filename']
return file_name
- get_media_requests()。它的第一個參數 item 是爬取生成的 Item 對象。我們將它的 url 字段取出來,然后直接生成 Request 對象。此 Request 加入調度隊列,等待被調度,執行下載。
- item_completed(),它是當單個 Item 完成下載時的處理方法。因為可能有個別圖片未成功下載,所以需要分析下載結果并剔除下載失敗的圖片。該方法的第一個參數 results 就是該 Item 對應的下載結果,它是一個列表形式,列表每一個元素是一個元組,其中包含了下載成功或失敗的信息。這里我們遍歷下載結果找出所有成功的下載列表。如果列表為空,那么說明該 Item 對應的圖片下載失敗了,隨即拋出異常DropItem,該 Item 忽略。否則返回該 Item,說明此 Item 有效。
以上兩個函數即可下載圖片了,圖片名稱為自動已哈希值命名,如:
0db6e07054d966513f0a6f315b687f205c7ced90.jpg 這種命名方式不友好,所以我們需要重寫 file_path函數,自定義圖片名稱。
- file_path():它的第一個參數 request 就是當前下載對應的 Request 對象。這個方法用來返回保存的文件名,接收上面meta傳遞過來的圖片名稱,將圖片以原來的名稱和定義格式進行保存。
5、編寫執行文件run.py運行
在項目下新建run.py作為執行文件
from scrapy import cmdline
#cmdline.execute('scrapy crawl image_download --nolog'.split())
cmdline.execute('scrapy crawl image_download'.split())
運行此文件,執行結果,在目錄下載第一頁壁紙完成。
六、小結
除了 ImagesPipeline 處理圖片外,還有 FilesPipeline 可以處理文件,使用方法與圖片類似,事實上 ImagesPipeline 是 FilesPipeline 的子類,因為圖片也是文件的一種。
Scrapy很強大,對于大型網站非常實用,還可以同時運行多個爬蟲程序,提升效率。Scrapy還有很多功能,可以自己研究。