Python爬蟲5.1 — scrapy框架簡單入門
綜述
本系列文檔用於對Python爬蟲技術的學習進行簡單的教程講解,鞏固自己技術知識的同時,萬一一不小心又正好對你有用那就更好了。
Python 版本是3.7.4
前面我們學習都是爬蟲相關的一些基本知識,學會掌握了前面的技術知識我們可以解決90%爬蟲相關的問題。但是我們如何更快更高效的解決這些問題呢(不光是開發快還有請求處理爬取快),這就用到了框架。下面我們開始一步一步學習Scrapy框架。
Scrapy 框架
Scrapy 框架介紹
寫一個爬蟲,需要做很多的事情。比如:發送網絡請求、數據解析、數據存儲、反反爬蟲機制(更換ip代理、設置請求頭等)、異步請求 等。這些工作如果每次都要自己從零開始寫的話,比較浪費時間。因此Scrapy
把一些基礎的東西都封裝好了,在它上面開發爬蟲可以變得更加的高效(爬取效率和開發效率)。因此真正在公司裏,一些上了量的爬蟲,都是使用Scrapy
框架來解決(關於框架的概念在這裏就不再做說明)。
Scrapy 架構圖
Scrapy 框架模塊功能
Scrapy Engine(引擎)
: Scrapy框架的核心,負責在Spider
和Item Pipeline
、Downloader
、Scheduler
中間通信、傳輸數據等。Spider(爬蟲)
: 發送需要爬取的鏈接給引擎,最後引擎把其他模塊請求回來的數據再發給爬蟲,爬蟲就去解析想要的數據。這部分是我們開發者自己寫的,因爲要爬取哪些鏈接,頁面中的哪些數據是我們需要的,都是由程序員自己決定。Scheduler(調度器)
: 複製接收引擎發送過來的請求,並按照一定的方式進行排列和整理,負責調度請求的順序等。Downloader(下載器)
: 負責接收引擎傳過來的下載請求,然後去網絡上下載對應的數據在交還給引擎。Item Pipeline(管道)
: 負責將Spider(爬蟲)
傳遞過來的數據進行保存,具體保存在哪裏,因該看開發者自己的需求。Downloader Middlewares(下載中間件)
: 可以擴展下載器和引擎之間通信功能的中間件。Spider Middlewares(Spider中間件)
: 可以擴展引擎和爬蟲之間通信功能的中間件。
Scrapy 執行流程
Scrapy中的數據流由執行引擎控制,其過程如下:
- 引擎從Spiders中獲取到最初的要爬取的請求(Requests);
- 引擎安排請求(Requests)到調度器中,並向調度器請求下一個要爬取的請求(Requests);
- 調度器返回下一個要爬取的請求(Requests)給引擎;
- 引擎將上步中得到的請求(Requests)通過下載器中間件(Downloader Middlewares)發送給下載器(Downloader ),這個過程中下載器中間件(Downloader Middlewares)中的process_request()函數會被調用到;
- 一旦頁面下載完畢,下載器生成一個該頁面的Response,並將其通過下載中間件(Downloader Middlewares)發送給引擎,這個過程中下載器中間件(Downloader Middlewares)中的process_response()函數會被調用到;
- 引擎從下載器中得到上步中的Response並通過Spider中間件(Spider Middlewares)發送給Spider處理,這個過程中Spider中間件(Spider Middlewares)中的process_spider_input()函數會被調用到;
- Spider處理Response並通過Spider中間件(Spider Middlewares)返回爬取到的Item及(跟進的)新的Request給引擎,這個過程中Spider中間件(Spider Middlewares)的process_spider_output()函數會被調用到;
- 引擎將上步中Spider處理的其爬取到的Item給Item 管道(Pipeline),將Spider處理的Request發送給調度器,並向調度器請求可能存在的下一個要爬取的請求(Requests);
- (從第二步)重複直到調度器中沒有更多的請求(Requests)。
Scrapy 安裝和文檔
- 安裝:通過
pip install scrapy
命令安裝即可。 - Scrapy官方文檔:http://doc.scrapy.org/en/latest
- Scrapy中文文檔:https://scrapy-chs.readthedocs.io/zh_CN/latest/index.html (文檔版本比較老,建議還是看官方文檔)
注意:
1. 在Ubuntu
上安裝Scrapy
之前需要先安裝以下依賴:
sudo apt-get install python-dev python-pip libxml2-dev libxslt1-dev zliblg-dev libffi-dev libssl-dev
然後再通過pip install scrapy
安裝。
2. 如果在Windows
系統下,提示這個錯誤ModuleNotFoundError:No module named 'win32api'
,那麼使用以下命令 可以解決:pip install pypiwin32
。
Scrapy 快速入門
創建項目
要使用Scrapy
框架創建目錄,需要通過命令來創建,首先進入到你想把這個項目存放的目錄。然後使用以下命令創建:
scrapy startproject [項目名稱]
不一定非要使用命令來創建,也可以按照其項目目錄結構進行手動創建,但是手動創建不是很方便並且創建起來很麻煩。
創建爬蟲
進入到項目所在的路徑,執行命令:
scrapy genspider [爬蟲名稱] [爬蟲的域名]
(注意:爬蟲的名稱不能和項目名稱一樣)。
目錄介紹
下面爲項目主要的文件目錄及文件作用:
project_folder -- 項目文件夾名稱
|
├──project_name -- 該項目的python模塊,一般和項目文件夾名稱相同
| |
| ├──spider -- 放置spider代碼的包,以後所有的爬蟲,都存放在這個裏面
| |
| ├──items.py -- 用來存放爬蟲怕寫來的數據的模型
| |
| ├──middlewares.py -- 用來存放各種中間件的文件
| |
| ├──pipelines.py -- 用來對items裏面提取的數據做進一步處理,如保存到本地磁盤等
| |
| ├──settings.py -- 本爬蟲的一些配置信息(如請求頭、多久發送一次請求、ip代理池等)
|
├──scrapy.cfg -- 項目的配置文件
使用Scrapy框架爬取糗事百科
使用命令創建糗百爬蟲
創建一個名字叫做qsbk
的爬蟲,並且能爬取的網頁只會限制在qiushibaike.com
這個域名下(注意:爬蟲的名稱不能和項目名稱一樣)。
scrapy genspider qsbk_spider 'qiushibaike.com'
爬蟲代碼解析
- items.py文件代碼
import scrapy
class QsbkItem(scrapy.Item):
# define the fields for your item here like:
# 定義item數據字段
author = scrapy.Field()
content = scrapy.Field()
- qsbk_spider.py文件代碼
import scrapy
from qsbk.items import QsbkItem
class QsbkSpiderSpider(scrapy.Spider):
name = 'qsbk_spider'
allowed_domains = ['qiushibaike.com']
start_urls = ['https://www.qiushibaike.com/text/page/1/']
def parse(self, response):
# SelectorList
# 解析頁面
content_left = response.xpath('//div[@id="content-left"]/div')
# 提取數據
for dz_div in content_left:
# Selector
author = dz_div.xpath(".//h2/text()").get().strip()
content_tmp = dz_div.xpath(".//div[@class='content']//text()").getall()
content = ''.join(content_tmp).strip()
item = QsbkItem(author=author, content=content)
# 使用yield返回給pipeline
yield item
- pipelines.py文件代碼
import json
class QsbkPipeline(object):
def __init__(self):
"""
打開文件,也可放在open_spider中
"""
self.fp = open('duanzi.json', 'w', encoding='utf-8')
def open_spider(self, spider):
"""
爬蟲被打開的時候執行
:param spider:
:return:
"""
print("爬蟲開始....")
def process_item(self, item, spider):
"""
爬蟲有item傳過來的時候會被調用
:param item:
:param spider:
:return:
"""
item_json = json.dumps(dict(item), ensure_ascii=False)
# 數據寫入文件
self.fp.write(item_json + '\n')
return item
def close_spider(self, spider):
"""
爬蟲關閉的時候被調用
:param spider:
:return:
"""
# 關閉文件
self.fp.close()
print("爬蟲結束....")
運行爬蟲
運行爬蟲只要我們運行命令即可:
scrapy crawl qsbk_spider
每次運行爬蟲我們都需要執行命令,所以我們可以在項目根目錄下創建一個文件start.py
l裏面放置需要執行的命令,文件代碼如下:
from scrapy import cmdline
cmdline.execute("scrapy crawl qsbk_spider".split())
# execute裏面需要傳遞列表數據,等價於
# cmdline.execute(["scrapy", "crawl", "qsbk_spider"])
運行爬蟲會打印很多log信息,我們可以在setting.py
文件中進行設置僅打印WARNING
等級以上的錯誤。在文件中增加LOG_LEVEL = 'WARNING'
配置即可。
糗事百科Scrapy爬蟲筆記
- response是一個
scrapy.http.response.html.HtmlResponse
對象,可以執行xpath
和css
語法來提取數據。 - 提取出來的數據是一個
Selector
或者SelectorList
對象,如果想要獲取其中的字符串,那麼應該執行getall()
或者get()
方法。 getall()
方法:獲取Selector
中所有文本,返回的是一個列表。get()
方法:獲取的是Selector
中的第一個文本,返回德是一個string類型。- 如果數據解析回來,要傳給
pipeline
處理,那個可以使用yield
來返回。或者是收集所有的item,最後統一使用return
返回。 - item:建議在
items.py
中定義好模型,以後就不要使用字典。 - pipeline:這個是專門用來保存詩句的,其中三個方法是會經常使用的:
open_spider(self,spider)
:當爬蟲被打開的時候執行;process_item(self,item,spider)
:當爬蟲有item傳過來的時候會被調用;close_spider(self,spider)
:當爬蟲關閉的時候被調用。- 要激活pipeline,應該在
setting.py
中,設置ITEM_PIPELINES
。示例如下:
ITEM_PIPELINES = { 'qsbk.pipelines.QsbkPipeline': 300, }
優化實例爬蟲數據存儲(Scrapy導出器)
上面例子我們在進行存儲數據的時候需要先將字典轉換成json,然後再做一些其他的存儲處理操作,這樣有點麻煩。我們可以使用Scrapy框架自帶的Exporter導出器scrapy.exporters
,其中就有一個JSON的導出模塊JsonItemExporter
。
修改pipelines.py文件如下:
# 引入JsonItemExporter類庫
from scrapy.exporters import JsonItemExporter
class QsbkPipeline(object):
def __init__(self):
"""
打開文件,也可放在open_spider中
"""
self.fp = open('duanzi1.json', 'wb')
# 初始化導出器
# 導出文件必須以二進制打開
self.exporter = JsonItemExporter(self.fp, ensure_ascii=False, encoding='utf-8')
def open_spider(self, spider):
"""
爬蟲被打開的時候執行
:param spider:
:return:
"""
print("爬蟲開始....")
def process_item(self, item, spider):
"""
爬蟲有item傳過來的時候會被調用
:param item:
:param spider:
:return:
"""
# 進行導出
self.exporter.export_item(item)
return item
def close_spider(self, spider):
"""
爬蟲關閉的時候被調用
:param spider:
:return:
"""
# 完成導出
self.exporter.finish_exporting()
# 關閉文件
self.fp.close()
print("爬蟲結束....")
我們在使用JsonItemExporter
導出器的時候,它是把所有的數據都當成列表中的一項進行導出。這種方式有一個缺陷,它的導出過程是先把你傳回來的所有字典保存在一個列表當中,在最後執行finish_exporting()
的時候再統一寫到json文件中。那麼這樣的話如果傳入的json數據比較大的話就不是很好了,它會把所有要導出的json先放到內存中,如果內存不夠大的話就對內存的使用不是很友好。
所以我們可以使用另外一種導出器的使用JsonLinesItemExporter
,我們使用這種就像第一次數據處理時的效果,它會將json每行寫入到文件中。
修改pipelines.py文件如下:
# 引入JsonLinesItemExporter類庫
from scrapy.exporters import JsonLinesItemExporter
class QsbkPipeline(object):
def __init__(self):
"""
打開文件,也可放在open_spider中
"""
self.fp = open('duanzi2.json', 'wb')
# 初始化導出器
# 導出文件必須以二進制打開
self.exporter = JsonLinesItemExporter(self.fp, ensure_ascii=False, encoding='utf-8')
def open_spider(self, spider):
"""
爬蟲被打開的時候執行
:param spider:
:return:
"""
print("爬蟲開始....")
def process_item(self, item, spider):
"""
爬蟲有item傳過來的時候會被調用
:param item:
:param spider:
:return:
"""
# 進行導出
self.exporter.export_item(item)
return item
def close_spider(self, spider):
"""
爬蟲關閉的時候被調用
:param spider:
:return:
"""
# 關閉文件
self.fp.close()
print("爬蟲結束....")
Scrapy導出器不光有JSON格式導出,還有XML、CSV、PICK等方式格式化數據導出。
JsonItemExporter和JsonLinesItemExporter
保存Json數據的時候,可以使用這兩個類,讓操作變得更簡單:
JsonItemExporter
: 這個時每次把數據添加到內存中,最後統一寫入到磁盤中,好處時存儲的數據是一個滿足json規則的數據,壞處是如果數據量比較大,那麼比較耗內存。JsonLinesItemExporter
: 這個每次調用export_item
的時候就把這個item存儲到硬盤中。患處是每一個字典是一行,整個文件不是一個滿足json格式的文件,好處是每次處理數據的時候就直接存儲到了硬盤中,這樣就不會耗內存。數據也比較安全。
爬取多頁數據
修改qsbk_spider.py文件如下即可:
import scrapy
from qsbk.items import QsbkItem
class QsbkSpiderSpider(scrapy.Spider):
name = 'qsbk_spider'
allowed_domains = ['qiushibaike.com']
start_urls = ['https://www.qiushibaike.com/text/page/1/']
base_url = 'https://www.qiushibaike.com'
def parse(self, response):
# SelectorList
# 解析頁面
content_left = response.xpath('//div[@id="content-left"]/div')
# 提取數據
for dz_div in content_left:
# Selector
author = dz_div.xpath(".//h2/text()").get().strip()
content_tmp = dz_div.xpath(".//div[@class='content']//text()").getall()
content = ''.join(content_tmp).strip()
item = QsbkItem(author=author, content=content)
# 使用yield返回給pipeline
yield item
# 獲取下一頁地址
next_url = response.xpath('//ul[@class="pagination"]/li[last()]/a/@href').get()
if not next_url:
# 沒有下一頁地址結束爬蟲
return
else:
# 將下一頁請求返回給調度器
yield scrapy.Request(self.base_url + next_url, callback=self.parse)
日誌設置
需另寫文章進行記錄日誌學習
其他博文鏈接
- Python爬蟲1.1 — urllib基礎用法教程
- Python爬蟲1.2 — urllib高級用法教程
- Python爬蟲1.3 — requests基礎用法教程
- Python爬蟲1.4 — requests高級用法教程
- Python爬蟲2.1 — BeautifulSoup用法教程
- Python爬蟲2.2 — xpath用法教程
- Python爬蟲3.1 — json用法教程
- Python爬蟲3.2 — csv用法教程
- Python爬蟲3.3 — txt用法教程
- Python爬蟲4.1 — threading(多線程)用法教程
- Python爬蟲4.2 — ajax(動態網頁數據抓取)用法教程
- Python爬蟲4.3 — selenium基礎用法教程
- Python爬蟲4.4 — selenium高級用法教程
Python爬蟲4.5 — tesseract(圖片驗證碼識別)用法教程