【0基礎學爬蟲】爬蟲基礎之scrapy的使用
大數據時代,各行各業對數據采集的需求日益增多,網絡爬蟲的運用也更為廣泛,越來越多的人開始學習網絡爬蟲這項技術,K哥爬蟲此前已經推出不少爬蟲進階、逆向相關文章,為實現從易到難全方位覆蓋,特設【0基礎學爬蟲】專欄,幫助小白快速入門爬蟲,本期為自動化工具 Selenium 的使用。
scrapy簡介
Scrapy 是一個用于爬取網站并提取結構化數據的強大且靈活的開源框架。它提供了簡單易用的工具和組件,使開發者能夠定義爬蟲、調度請求、處理響應并存儲提取的數據。Scrapy 具有高效的異步處理能力,支持分布式爬取,通過其中間件和擴展機制可以方便地定制和擴展功能,廣泛應用于數據挖掘、信息聚合和自動化測試等領域。
scrapy 工作流程
1、啟動爬蟲:Scrapy 啟動并激活爬蟲,從初始URL開始爬取。
2、調度請求:爬蟲生成初始請求,并將其發送給調度器。
3、下載頁面:調度器將請求發送給下載器,下載器從互聯網獲取頁面。
4、處理響應:下載器將響應返回給引擎,傳遞給爬蟲。
5、提取數據:爬蟲從響應中提取數據(items)和更多的URL(新的請求)。
6、處理數據:提取的數據通過項目管道進行處理,清洗并存儲。
7、繼續爬取:新的請求被調度器處理,繼續下載和提取數據,直到所有請求處理完畢。
scrapy 每個模塊的具體作用
安裝scrapy
pip install scrapy
安裝成功后,直接在命令終端輸入 scrapy ,輸出內容如下:
新建scrapy項目
使用 scrapy startproject + 項目名 創建新項目。
這里我們使用 scrapy startproject scrapy_demo
創建項目示例:
然后通過下面命令創建我們的爬蟲模板,這里就按照scrapy 給出的實例創建:
cd scrapy_demo
scrapy genspider example example.com
使用pycharm 打開我們的項目,項目格式如下:
各個文件夾的含義:
spiders:存放爬蟲文件
items:定義爬取的數據結構
middlewares:定義下載中間件和爬蟲中間件。中間件是處理請求和響應的鉤子,可以修改請求、響應、異常等
pipelines:定義管道,用于處理爬蟲提取的數據,例如數據清洗、驗證和存儲等操作。
settings:定義了項目的基本配置
使用scrapy
這里以我們熟悉的某瓣為例來說明 scrapy
的用法。
修改 example.py 文件:
import scrapyclass ExampleSpider(scrapy.Spider):name = "example"# allowed_domains = ["example.com"] # 允許爬取的網站范圍,可以不要start_urls = ["https://movie.douban.com/top250"]def parse(self, response):print(response.text)
在終端輸入 scrapy crawl example
運行結果如下:
輸出了很多信息,包含版本號、插件、啟用的中間件等信息。
Versions:版本信息,包括scrapy和其它庫的版本信息
Overridden settings: 重寫的相關配置
Enabled downloader middlewares:開啟的下載器中間件
Enabled spider middlewares:開啟的爬蟲中間件
Enabled item pipelines:開啟的管道
Telnet Password:Telnet 平臺密碼(Scrapy附帶一個內置的telnet控制臺,用于檢查和控制Scrapy運行過程)
Enabled extensions :開啟的拓展功能
Dumping Scrapy stats:所以的信息匯總
我們重點看這里:
可以發現,我們返回了403狀態碼,原因是因為我們少了請求頭和有robots協議。
在 setting.py 增加請求頭、修改 robots 協議:
# Obey robots.txt rules
ROBOTSTXT_OBEY = False # 這里改成False,表示不遵守robots協議# Override the default request headers:
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/125.0.0.0 Safari/537.36 Edg/125.0.0.0"
} # 然后把這個放開,這個表示該項目的默認請求頭
運行之后,可以發現能正常返回 html 頁面數據。
scrapy 運行項目的兩種方式
上面我們是通過終端運行的,下面我們使用 python 運行。
修改 example.py 文件代碼:
import scrapy
from scrapy import cmdlineclass ExampleSpider(scrapy.Spider):name = "example"# allowed_domains = ["example.com"] # 允許爬取的網站范圍,可以不要start_urls = ["https://movie.douban.com/top250"]def parse(self, response):print(response.text)if __name__ == '__main__':cmdline.execute("scrapy crawl example".split())# cmdline.execute("scrapy crawl example --nolog".split()) 不輸出提示信息
如果不想輸出與爬蟲無關的信息,可以在后面加上 --nolog 命令,這樣就不會打印提示信息了。
數據翻頁抓取
scrapy實現翻頁請求
我們可以直接利用scrapy 內置的數據解析方法對數據進行抓取:
代碼如下:
import scrapy
from scrapy import cmdlineclass ExampleSpider(scrapy.Spider):name = "example"# allowed_domains = ["example.com"] # 允許爬取的網站范圍,可以不要start_urls = ["https://movie.douban.com/top250"]def parse(self, response):print(response.text)ol_list = response.xpath('//ol[@class="grid_view"]/li')for ol in ol_list:item = {}# 利用scrapy封裝好的xpath選擇器定位元素,并通過extract()或extract_first()來獲取結果item['title'] = ol.xpath('.//div[@class="hd"]/a/span[1]/text()').extract_first()item['rating'] = ol.xpath('.//div[@class="bd"]/div/span[2]/text()').extract_first()item['quote'] = ol.xpath('.//div[@class="bd"]//p[@class="quote"]/span/text()').extract_first()print(item)if __name__ == '__main__':cmdline.execute("scrapy crawl example --nolog".split())# cmdline.execute("scrapy crawl example".split())
上面只抓取到了第一頁,那么我們怎么抓取后面的每一頁呢?
這里介紹兩種方式:
1、利用callback 參數,進入項目源碼,找到Request請求對象:
Request 對象含義如下:
參數 | 描述 |
---|---|
url (str) | 請求的 URL。 |
callback (callable) | 用于處理該請求的回調函數。默認是 parse 方法。 |
method (str) | HTTP 請求方法,如 'GET' , 'POST' 等。默認為 'GET' 。 |
headers (dict) | 請求頭信息。 |
body (bytes or str) | 請求體,通常在 POST 請求中使用。 |
cookies (dict or list) | 請求攜帶的 Cookies,可以是一個字典或字典的列表。 |
meta (dict) | 該請求的元數據字典,用于在不同請求之間傳遞數據。 |
encoding (str) | 請求的編碼格式。默認為 'utf-8' 。 |
priority (int) | 請求的優先級,默認值為 0。優先級值越高,優先級越高。 |
callback 就是回調函數,接收一個函數名為參數。
實現如下:
def parse(self, response):print(response.text)ol_list = response.xpath('//ol[@class="grid_view"]/li')for ol in ol_list:item = {}# extract_first() 提取第一個元素item['title'] = ol.xpath('.//div[@class="hd"]/a/span[1]/text()').extract_first()item['rating'] = ol.xpath('.//div[@class="bd"]/div/span[2]/text()').extract_first()item['quote'] = ol.xpath('.//div[@class="bd"]//p[@class="quote"]/span/text()').extract_first()print(item)if response.xpath("//a[text()='后頁>']/@href").extract_first() is not None:next_url = response.urljoin(response.xpath("//a[text()='后頁>']/@href").extract_first())print(next_url)yield scrapy.Request(url=next_url, callback=self.parse)
2、重寫 start_requests 方法:
代碼如下:
def start_requests(self):for i in range(0, 5):url = 'https://movie.douban.com/top250?start={}&filter='.format(i * 25)yield scrapy.Request(url)def parse(self, response):ol_list = response.xpath('//ol[@class="grid_view"]/li')for ol in ol_list:item = {}# extract_first() 提取第一個元素item['title'] = ol.xpath('.//div[@class="hd"]/a/span[1]/text()').extract_first()item['rating'] = ol.xpath('.//div[@class="bd"]/div/span[2]/text()').extract_first()item['quote'] = ol.xpath('.//div[@class="bd"]//p[@class="quote"]/span/text()').extract_first()print(item)
Responses 對象含義如下:
參數 | 描述 |
---|---|
url (str) | 響應的 URL。 |
status (int) | HTTP 響應狀態碼。 |
headers (dict) | 響應頭信息。 |
body (bytes) | 響應體內容,二進制格式。 |
flags (list) | 響應的標志列表。 |
request (Request) | 生成此響應的請求對象。 |
meta (dict) | 該請求的元數據字典,用于在不同請求之間傳遞數據。 |
encoding (str) | 響應的編碼格式。通常由 Scrapy 自動檢測,但可以手動設置。 |
text (str) | 響應體內容,解碼為字符串格式。 |
css (callable) | 選擇器,用于通過 CSS 表達式提取數據。 |
xpath (callable) | 選擇器,用于通過 XPath 表達式提取數據。 |
json (callable) | 解析 JSON 響應體并返回字典或列表。 |
數據定義
數據爬取下來之后,我們通過scrapy 的 items 進行操作。item就是即提前規劃好哪些字段需要抓取,比如上面的標題、評分這些字段就需要使用 item 提前定義好。
Scrapy Item 的作用
- 結構化數據:通過定義 Item,可以明確抓取數據的結構。例如,一個商品的信息可能包含名稱、價格、庫存等字段。
- 數據驗證:可以在 Item 中定義字段的類型和驗證規則,確保抓取的數據符合預期。
- 代碼可讀性:通過定義 Item,可以使代碼更具可讀性和可維護性,清晰地了解抓取的數據結構。
定義item
item.py 編寫如下:
import scrapyclass ScrapyDemoItem(scrapy.Item):# define the fields for your item here like:# name = scrapy.Field()title = scrapy.Field()rating = scrapy.Field()quote = scrapy.Field()
使用item
使用 item 需要先實例化,使用方法和 python 字典方式一樣
在example.py 導入我們需要使用的 item 類,這里我們就用默認的 ScrapyDemoItem 類
import scrapy
from scrapy import cmdline
from scrapy_demo.items import ScrapyDemoItemclass ExampleSpider(scrapy.Spider):name = "example"def start_requests(self):for i in range(0, 5):url = 'https://movie.douban.com/top250?start={}&filter='.format(i * 25)yield scrapy.Request(url)def parse(self, response):ol_list = response.xpath('//ol[@class="grid_view"]/li')for ol in ol_list:item = ScrapyDemoItem()# extract_first() 提取第一個元素item['title'] = ol.xpath('.//div[@class="hd"]/a/span[1]/text()').extract_first()item['rating'] = ol.xpath('.//div[@class="bd"]/div/span[2]/text()').extract_first()item['quote'] = ol.xpath('.//div[@class="bd"]//p[@class="quote"]/span/text()').extract_first()print(item)if __name__ == '__main__':cmdline.execute("scrapy crawl example --nolog".split())
數據存儲
Scrapy Pipeline 的作用
- 數據清洗和驗證:你可以在 pipeline 中編寫代碼來清洗和驗證數據。例如,去除空白字符、處理缺失值、驗證數據格式等。
- 去重:可以檢查和去除重復的數據項,確保最終的數據集是唯一的。
- 存儲:將處理過的數據存儲到不同的存儲后端,如數據庫(MySQL、MongoDB)
- 進一步處理:執行復雜的轉換、聚合等操作,以便在存儲之前對數據進行進一步處理。
編寫Pipeline
這里我們使用mysql 進行數據保存。
pipeline.py
import pymysql
from itemadapter import ItemAdapter
class MysqlPipeline:def __init__(self):self.connection = pymysql.connect(user='root', # 換上你自己的賬密和數據庫 password='root', db='scrapy_demo',)self.cursor = self.connection.cursor()self.create_table()def create_table(self):table = """CREATE TABLE IF NOT EXISTS douban (id INT AUTO_INCREMENT PRIMARY KEY,title VARCHAR(255) NOT NULL,rating FLOAT NOT NULL,quote TEXT)CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"""self.cursor.execute(table)self.connection.commit()def process_item(self, item, spider):try:self.cursor.execute("INSERT INTO douban(id,title, rating, quote) VALUES (%s,%s, %s, %s)",(0, item['title'], item['rating'], item['quote']))self.connection.commit()except pymysql.MySQLError as e:spider.logger.error(f"Error saving item: {e}")print(e)return itemdef close_spider(self, spider):self.cursor.close()self.connection.close()
settings.py
ITEM_PIPELINES = {"scrapy_demo.pipelines.MysqlPipeline": 300,
} # 放開Item
配置好后,運行example 就能看到我們的數據被正確入庫了。
數據不止能存儲mysql,還存儲到mongo、csv等等,感興趣的小伙伴可以查看官方文檔,有很詳細的教程。
scrapy 中間件
scrapy中間件的分類和作用
根據scrapy運行流程中所在位置不同分為:
- 下載中間件
- 爬蟲中間件
Scrapy 中間件 (middlewares) 的作用是處理 Scrapy 請求和響應的鉤子(hook),允許你在它們被scrapy引擎處理前或處理后對它們進行處理和修改。中間件為用戶提供了一種方式,可以在請求和響應的不同階段插入自定義邏輯。
一般我們常用的是下載中間件,所以下面我們用下載中間件來說明用法。
middlewares.py
Downloader Middlewares默認的方法:- process_request(self, request, spider):- 當每個request通過下載中間件時,該方法被調用。- 返回None值:繼續請求- 返回Response對象:不再請求,把response返回給引擎- 返回Request對象:把request對象交給調度器進行后續的請求
- process_response(self, request, response, spider):- 當下載器完成http請求,傳遞響應給引擎的時候調用- 返回Resposne:交給process_response來處理- 返回Request對象:交給調取器繼續請求
- from_crawler(cls, crawler):- 類似于init初始化方法,只不過這里使用的classmethod類方法- 可以直接crawler.settings獲得參數,也可以搭配信號使用
自定義隨機ua
我們借助 feapder 給我們封裝好的 ua 來進行測試:
middlewares.py
from feapder.network import user_agent
class ScrapyDemoDownloaderMiddleware:def process_request(self, request, spider):request.headers['User-Agent'] = user_agent.get()return None
settings.py
DOWNLOADER_MIDDLEWARES = {"scrapy_demo.middlewares.ScrapyDemoDownloaderMiddleware": 543,
} #放開下載中間件
example.py
import scrapy
from scrapy import cmdlineclass ExampleSpider(scrapy.Spider):name = "example"start_urls = ["https://movie.douban.com/top250"]def parse(self, response):print(response.request.headers)if __name__ == '__main__':cmdline.execute("scrapy crawl example --nolog".split())
可以發現每次輸出的 ua 不一樣。
自定義代理
通過Request 對象的 mata 參數來設置代理,這里以本地的 7890 端口為例:
middlewares.py
def process_request(self, request, spider):request.headers['User-Agent'] = user_agent.get()request.meta['proxy'] = "http://127.0.0.1:7890"return None
中間件權重
當涉及到多個中間件的時候,請求時數字越小權重越高,越先執行 ,響應時數字越大越先執行。這里我們可以借助scrapy 流程圖來理解,誰離scrapy engine 引擎越近,表明權重越高。
這里我們創建兩個類來測試一下:
middlewares.py
class OneMiddleware(object):def process_request(self, request, spider):print('one 請求')def process_response(self, request, response, spider):print('one 響應')# return Noneclass TwoMiddleware(object):def process_request(self, request, spider):print('two 請求')def process_response(self, request, response, spider):print('two 響應')return response
settings.py
DOWNLOADER_MIDDLEWARES = {"scrapy_demo.middlewares.OneMiddleware": 543,"scrapy_demo.middlewares.TwoMiddleware": 544
}
運行 example.py 輸出如下結果:
scrapy-redis 組件
Scrapy-Redis 是 Scrapy 的一個擴展,允許你使用 Redis 作為爬蟲隊列,并共享爬蟲狀態:
安裝
pip install scrapy-redis
注意:這里scrapy 版本需要替換成 2.9.0版本或者2.0.0以下,不然會報錯:
TypeError: crawl() got an unexpected keyword argument 'spider'
因為新版本已經不支持了。
然后新建 一個 redis_demo 爬蟲
scrapy genspider redis_demo redis_demo.com
配置 scrapy-redis
settings.py
加入下面代碼
# 設置 Redis 主機和端口
REDIS_URL = 'redis://127.0.0.1:6379/0'
# 使用 Scrapy-Redis 的調度器
SCHEDULER = "scrapy_redis.scheduler.Scheduler"# 使用 Scrapy-Redis 的去重器
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"開啟redis管道
ITEM_PIPELINES = {"scrapy_redis.pipelines.RedisPipeline": 301
}
redis_demo.py
from scrapy_redis.spiders import RedisSpider
from scrapy import cmdline# 繼承scrapy——redis 類,實現分布式
class RedisDemoSpider(RedisSpider):name = "redis_demo"redis_key = "redis_demo:start_urls" # redis keydef parse(self, response):ol_list = response.xpath('//ol[@class="grid_view"]/li')for ol in ol_list:item = {}# extract_first() 提取第一個元素item['title'] = ol.xpath('.//div[@class="hd"]/a/span[1]/text()').extract_first()item['rating'] = ol.xpath('.//div[@class="bd"]/div/span[2]/text()').extract_first()item['quote'] = ol.xpath('.//div[@class="bd"]//p[@class="quote"]/span/text()').extract_first()print(item)yield itemif __name__ == '__main__':cmdline.execute("scrapy crawl redis_demo".split())
運行后會發現已經在監聽端口了:
這時我們新建一個demo 文件:
import redisr = redis.Redis(db=0)
r.lpush('redis_demo:start_urls',"https://movie.douban.com/top250")
#r.lpush('redis_demo:start_urls',"https://movie.douban.com/top250?start=25&filter=")
然后運行這個demo.py文件,會發現數據已經成功入庫了:
我們打開redis 可視化工具進行查看:
但是現在當我們每次跑一個地址的時候,原來的數據就沒有了,要想解決這個問題,我們就得運用到scrapy-redis的持久化存儲了。
redis 持久化存儲
Scrapy-Redis 默認會在爬取全部完成后清空爬取隊列和去重指紋集合。初始第一個網址一定會進行請求,后面的重復方式不會進行請求。
如果不想自動清空爬取隊列和去重指紋集合,我們在 settings.py 增加如下配置:
SCHEDULER_PERSIST = True #如果需要持久化爬取狀態,可以開啟
再次運行 redis_demo.py ,然后運行兩次demo.py文件可以測試一下:
至此,完成了持久化存儲。
redis 分布式
要想在多臺電腦跑同一個程序,只需要把其它電腦的 redis 連接到一臺就行。
settings.py
# 設置 Redis 主機和端口
REDIS_URL = '這里寫你的遠程電腦ip地址'
# 使用 Scrapy-Redis 的調度器
SCHEDULER = "scrapy_redis.scheduler.Scheduler"# 使用 Scrapy-Redis 的去重器
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"開啟redis管道
ITEM_PIPELINES = {"scrapy_redis.pipelines.RedisPipeline": 301
}