楔子
Scrapy是一個為了爬取網站資料,提取結構性資料而編寫的應用框架。可以應用在包括資料探勘,資訊處理或儲存歷史資料等一系列的程式中。
安裝
方式1
pip install scrapy
方式2
如果方式1安裝不了,在看此方式,如果方式1能正常安裝,那就不需要檢視此方式了。
# 1. 安裝wheel檔案
pip3 install wheel
# 2. 安裝 lxml 解析器
pip3 install lxml
# 3. 安裝 pyopenssl
pip3 install pyopenssl
# 4. 下載並安裝pywin32
下載並安裝 pywin32 可以從其官網下載最適合您的版本:https://sourceforge.net/projects/pywin32/files/pywin32/
請根據您的作業系統和Python版本選擇正確的安裝檔案進行下載。
下載完成後,按照安裝嚮導進行安裝。
# 5.下載twisted的wheel檔案
要安裝 Scrapy,需要先下載 twisted 的 wheel 檔案。
可以從官方網站下載 twisted 的 wheel 檔案:http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted
請注意選擇與您的Python環境相匹配的 wheel 檔案進行下載。
# 6. 安裝twisted
下載完成 twisted 的 wheel 檔案後,可以使用以下命令安裝 twisted:
pip3 install 下載目錄\Twisted-17.9.0-cp36-cp36m-win_amd**.whl
將 下載目錄 替換為您實際下載 twisted wheel 檔案所在的目錄
並根據您的 Python 環境選擇正確的檔名進行替換。
報錯解決方案
scrapy的基本使用
檢視幫助 scrapy - h
- Project-only必須切到專案資料夾下才能執行
- Global的命令則不需要
Global commands:
startproject #建立專案
genspider #建立爬蟲程式
settings #如果是在專案目錄下,則得到的是該專案的配置
runspider #執行一個獨立的python檔案,不必建立專案
shell #scrapy shell url地址 在互動式除錯,如選擇器規則正確與否
fetch #獨立於程單純地爬取一個頁面,可以拿到請求頭
view #下載完畢後直接彈出瀏覽器,以此可以分辨出哪些資料是ajax請求
version #scrapy version 檢視scrapy的版本,scrapy version -v檢視scrapy依賴庫的版本
Project-only commands:
crawl #執行爬蟲,必須建立專案才行,確保配置檔案中ROBOTSTXT_OBEY = False
check #檢測專案中有無語法錯誤
list #列出專案中所包含的爬蟲名
parse #scrapy parse url地址 --callback 回撥函式 #以此可以驗證我們的回撥函式是否正確
bench #scrapy bentch壓力測試
建立專案
須知:
- 路徑不要包含中文
- 專案名稱不要以數字開頭
- 專案名稱不建議包含漢字
# cd 到某一個目錄下,然後執行 scrapy startproject 專案名
scrapy startproject spider
進入到專案裡面
切換到爬蟲專案錄
cd spider
建立spider專案
建立自定義的爬蟲程式指令碼
# scrapy genspider 自定義爬蟲名稱 目標網址
# 這裡的域名不需要新增協議,因為建立後scrapy會自動幫你加上協議
scrapy genspider QAQ www.QAQ.com
├── NewsPro # 專案名
│ ├── __init__.py
│ ├── items.py # 定義資料結構的地方,爬蟲的資料包含哪一些 通俗的說 你要下載的資料都有哪一些
│ ├── middlewares.py # 中介軟體,用於處理請求和響應 也可以設定代理
│ ├── pipelines.py # 管道,用於資料持久化等操作 一般用來處理下載的資料
│ ├── settings.py # 專案配置檔案,包含各種設定選項 比如robots協議 ua等
│ └── spiders # 存放自定義的爬蟲,類似於應用程式
│ ├── __init__.py
│ ├── huanqiu.py # 自定義的爬蟲檔案 核心功能檔案 *************
└── scrapy.cfg # Scrapy 專案配置檔案
items.py
:定義了專案中要抓取的資料模型,類似於 Django 中的 models.py 檔案。您可以在這裡定義專案需要爬取的資料結構。middlewares.py
:中介軟體,用於在請求傳送給伺服器之前或從伺服器返回給爬蟲之後進行處理。您可以在這裡新增各種中介軟體,例如代理、使用者代理等。pipelines.py
:管道,用於資料的後處理,比如資料的儲存、清洗等操作。每個管道元件都是獨立的,可以對資料進行不同的處理。settings.py
:專案配置檔案,包含了專案的各種配置選項,比如爬蟲的設定、中介軟體的設定、管道的設定等。您可以在這裡配置專案的各種引數。spiders
目錄:存放自定義的爬蟲檔案,用於實際的資料抓取工作。每個爬蟲檔案定義了一個獨立的爬蟲,用於從特定網站抓取資料。scrapy.cfg
:Scrapy 專案配置檔案,包含了專案的基本配置資訊,比如專案的名稱、版本號、部署設定等。
君子協議 robots.txt
擴充閱讀
# settings.py
# 預設為True,如果拿不到資料 改為False或者註釋掉即可
ROBOTSTXT_OBEY = True
簡單案例
# spider\mc.py
import scrapy
class McSpider(scrapy.Spider):
# 爬蟲的名字,用於執行爬蟲的時候 使用的值
name = "mc"
# 允許訪問的域名,也就是除了這個域名以及子域名之外,其他的域名都不允許訪問
allowed_domains = ["www.xx.com"]
# start_urls 是在allowed_domains前面加一個協議http/https, 後面加一個/【可選】
# 如果字尾是html的時候,後面是不允許加/的
start_urls = ["https://www.xx.com/"]
# parse是執行了start_urls之後執行的方法
# 方法中的response就是返回的那個物件
# 相當於 response = urllib.requests.urlopen()
# response = requests.get()
def parse(self, response):
print('小滿最棒啦!')
如何執行
# cd到剛剛的爬蟲專案的spider資料夾下,執行 scrapy crawl spider_name
scrapy crawl mc # 會列印日誌
scrapy crawl mc --nolog # 不列印日誌
scrapy的settings配置
基礎配置
#1 專案名字,整個爬蟲名字
BOT_NAME = "firstscrapy"
#2 爬蟲存放位置的設定
SPIDER_MODULES = ["firstscrapy.spiders"]
NEWSPIDER_MODULE = "firstscrapy.spiders"
#3 是否遵循爬蟲協議,一般都設為False
ROBOTSTXT_OBEY = False
# 4 User-Agent設定
USER_AGENT = "firstscrapy (+http://www.yourdomain.com)"
#5 日誌級別設定
LOG_LEVEL='ERROR'
#6 DEFAULT_REQUEST_HEADERS 預設請求頭
DEFAULT_REQUEST_HEADERS = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'en',
}
#7 SPIDER_MIDDLEWARES 爬蟲中介軟體
SPIDER_MIDDLEWARES = {
'cnblogs.middlewares.CnblogsSpiderMiddleware': 543,
}
#8 DOWNLOADER_MIDDLEWARES 下載中介軟體
DOWNLOADER_MIDDLEWARES = {
'cnblogs.middlewares.CnblogsDownloaderMiddleware': 543,
}
#9 ITEM_PIPELINES 持久化配置
ITEM_PIPELINES = {
'cnblogs.pipelines.CnblogsPipeline': 300,
}
高階配置
#1 增加併發數,預設為16,可以根據需求進行調整
# 預設scrapy開啟的併發執行緒為32個,可以適當進行增加。
# 值為100,併發設定成了為100。
# 在settings配置檔案中修改
CONCURRENT_REQUESTS = 100
#2 降低日誌級別,可設定為INFO或ERROR,減少日誌輸出,提高效能
# 在執行scrapy時,會有大量日誌資訊的輸出,為了減少CPU的使用率。
# 可以設定log輸出資訊為INFO或者ERROR即可。
在配置檔案中編寫:
LOG_LEVEL = 'INFO'
# 3 禁止使用Cookie,預設為True,如果不需要使用Cookie可以設定為False
# 如果不是真的需要cookie,則在scrapy爬取資料時可以禁止cookie從而減少CPU的使用率,提升爬取效率。
COOKIES_ENABLED = False
# 4 禁止重試,預設為True,如果不需要進行重試請求可以設定為False
# 對失敗的HTTP進行重新請求(重試)會減慢爬取速度,因此可以禁止重試。
RETRY_ENABLED = False
# 5 設定下載超時時間,預設180秒,可以根據需求進行調整
# 如果對一個非常慢的連結進行爬取,減少下載超時可以能讓卡住的連結快速被放棄,從而提升效率。
# 超時時間為10s
DOWNLOAD_TIMEOUT = 10
持久化儲存
parse的返回值
每個爬蟲中的
parse
方法以及其他後續解析方法通常需要返回一個可迭代物件(如列表),其中包含的是經過處理後的資料結構,通常是符合Item定義的字典。
def parse(self, response):
items = []
for element in response.css('some_selector'):
item = {
'field1': element.xpath('...').get(),
'field2': element.xpath('...').get(),
}
items.append(item)
return items
命令列匯出json和csv
# settings.py
FEED_FORMAT = 'json' # 選擇要匯出的格式,可以選擇 'json' 或 'csv'
FEED_URI = 'output_file.json' # 指定要儲存的檔案路徑,可以是 'output_file.json' 或 'output_file.csv'
# 匯出json檔案
scrapy crawl your_spider_name -o output.json
# 匯出csv
scrapy crawl your_spider_name -o output.csv -t csv
response的屬性和方法
response.text # 結果原始碼,同response.text 一樣 是字串
respnse.body # 解碼原始碼,同response.content 一樣 結果是二進位制
response.xpath() # 可以直接使用xpath語法來解析response中的內容 和正常的xpath一樣,所有的selector都可以再次進行xpath
response.extract() # 提取selector物件的data屬性值
response.extract_first() # 提取的是select列表的第一個資料
# 沒有寫extract()
[<Selector query='//div[@class="slist"]//li[1]' data='<li><a href="/tupian/34023.html" titl...'>]
# 寫了沒有寫extract()
['<li><a href="/tupian/34023.html" title="報紙牆女孩粉色頭髮4K動漫桌布" target="_blank"><span><img src="/uploads/allimg/240403/002821-1712075301366c.jpg" alt="報紙牆女孩粉色頭髮4K動漫桌布"></span><b>報紙牆女孩粉色頭髮4K動漫桌布</b></a></li>']
attributes # 請求的屬性,包括方法、URL、頭部資訊等
body # 響應的正文內容
cb_kwargs # 在回撥函式中傳遞額外的關鍵字引數
certificate # 響應中的 SSL 證書資訊
copy # 複製當前的 Response 物件
css # 使用 CSS 選擇器從響應中提取資料
encoding # 響應的編碼格式
flags # 請求的標誌,例如是否跟蹤重定向等
follow # 根據響應的狀態碼決定是否跟蹤重定向
follow_all # 跟蹤所有重定向
headers # 響應的頭部資訊
ip_address # 請求的 IP 地址
jmespath # 使用 JMESPath 查詢從響應中提取資料
json # 解析響應的 JSON 內容
meta # 請求和響應的後設資料資訊
protocol # 請求的協議,例如 HTTP 或 HTTPS
replace # 替換當前的 Request 物件
request # 當前的 Request 物件
selector # 使用 Selector 物件從響應中提取資料
status # 響應的狀態碼
text # 響應的文字內容
url # 請求的 URL
urljoin # 將相對 URL 轉換為絕對 URL
xpath # 使用 XPath 從響應中提取資料
CSS選擇器 response.css
# 獲取屬性 response.css("選擇器 ::('屬性值')")
response.css(".slist img ::attr('alt')") # 提取圖片的alt屬性
response.css(".slist b::text") # 提取文字的直接內容
extract()和extract_first()
extract()
和 extract_first()
方法都是用於從 Scrapy 的 Selector 物件中提取文字內容的方法。
extract()
方法會提取所有匹配的內容,並將其以列表的形式返回。如果有多個匹配項,將返回多個文字內容。extract_first()
方法會提取第一個匹配的內容,並將其作為字串返回。如果沒有匹配項,則返回None
。
這兩個方法通常與 XPath 或 CSS 選擇器一起使用,用於從網頁中提取所需的資料。例如:
# 使用 XPath 提取所有匹配的文字內容
texts = response.xpath('//div[@class="content"]/p/text()').extract()
# 使用 CSS 選擇器提取第一個匹配的文字內容
first_text = response.css('h1::text').extract_first()
在這個例子中,extract()
和 extract_first()
方法用於提取網頁中特定元素的文字內容,以供後續處理或儲存。
scrapy的工作原理
如果是資料,就交給管道下載,如果還是一個要請求的url,就在走一遍。
scrapy shell
- 什麼是scrapy shell?
- scrapy終端,是一個互動終端,供您在未啟動scrapy的情況下嘗試及除錯您的爬蟲程式碼。其本意是用來測試提取資料的程式碼,不過您可以將其作為正常的python終端,在上面測試任何的python程式碼。
- 該終端是用來測試XPath或CSS表示式,檢視他們的工作方式及從爬取的網頁中提取的資料,在編寫您的spider時,該終端提供了互動性測試您的表達程式碼的功能,免去了每次修改後執行spider的麻煩。
- 一旦熟悉了scrapy終端後,您會發現其在開發和除錯spider時發揮的巨大作用。
- 安裝ipython
- 安裝:pip install ipython
- 簡介:如果您安裝了ipython,scrapy的終端將使用ipython(替代標準python終端), ipython終端與其他相比更為強大,提供了智慧的自動補全,高亮輸出,及其他特性。
如何進入終端
# 直接在cmd視窗中 輸入 scrapy shell 域名
scrapy shell www.xx.com
yield
- 帶有yield的函式不再是一個普通函式,而是一個生成器generator,可用於迭代
- yield是一個類似return的關鍵字,迭代一次遇到yield時,就返回yield後面(右邊)的值,重點是:下一次迭代時,從上一次迭代遇到的yield後面的程式碼(下一行)開始執行
- 簡要理解:yield就是return返回的一個值,並且記住這個值的返回位置,下次迭代就從這個位置後(下一行)開始。
梳理流程
# spider\book.py
import scrapy
from ..items import DangdangItem
class BookSpider(scrapy.Spider):
name = "book"
allowed_domains = ["xxx"]
start_urls = ["xxx"]
def parse(self, response):
# src = '//ul[@id="component_59"]//li/a/img/@src'
# title = '//ul[@id="component_59"]//li/a/img/@alt'
# price = '//ul[@id="component_59"]//li/p[@class="price"]/span[1]/text()'
li_list = response.xpath('//ul[@id="component_59"]//li')
for li in li_list:
src = li.xpath('./a/img/@data-original').extract_first()
name = li.xpath('./a/img/@alt').extract_first()
price = li.xpath('./p[@class="price"]/span[1]/text()').extract_first()
# print(src, name, price)
# 這個物件要交給管道pipelines去下載
book = DangdangItem(src=src, name=name, price=price)
# 獲取一個book 就將book交給pipelines
yield book
# items.py
# 就相當於django裡面的模型層
class DangdangItem(scrapy.Item):
# 名字
name = scrapy.Field()
# 圖片
src = scrapy.Field()
# 價格
price = scrapy.Field()
# pipelines.py
# 如果想要使用管道的話,那麼就必須在settings中開啟管道
class DangdangPipeline:
# item 就是book後面的yield物件, 也就是你的spider下面的爬蟲返回的資料的
def process_item(self, item, spider):
# 以下這種方法不推薦 因為操作IO太過頻繁
# 1. write寫入必須是一個字串,而不能是其他的物件
# 2. w模式會覆蓋寫
with open('book.json', 'at', encoding='utf-8') as file:
file.write(str(item) + "\n")
# 預設的item型別是<class 'dangdang.items.DangdangItem'> 這裡可以使用字典強轉,然後操作就可以了
# data = dict(item)
return item
item
# 可以透過get取值,但是並不是字典的型別,參考上面
src = item.get('src')
name = item.get('name')
# 這個鍵就是你在item裡面定義的
open_spider和close_spider
# pipelines.py
class DangdangPipeline:
# 在爬蟲檔案開始之前就執行的一個方法
# 名字不能寫錯 要指定引數
def open_spider(self, spider):
print('+++++++++++++++++++++++++++++++++++++++++++++++')
def process_item(self, item, spider):
return item
# 在爬蟲執行完畢之後,執行的方法
def close_spider(self, spider):
print('-----------------------------------------------------')
"""
+++++++++++++++++++++++++++++++++++++++++++++++
下面全是spider裡面的程式碼
//img3m3.ddimg.cn/71/16/29687003-1_b_1711521731.jpg 驕陽似我(下)(親籤版) ¥32.00
//img3m2.ddimg.cn/46/8/29124262-1_b_5.jpg 那個不為人知的故事 ¥20.10
//img3m8.ddimg.cn/72/12/23945598-1_b_1706687985.jpg 大哥(套裝共2冊) ¥32.00
//img3m4.ddimg.cn/72/17/29687004-1_b_1711521722.jpg 驕陽似我(上下 兩冊套裝) ¥49.00
......
-----------------------------------------------------
"""
# 最佳化後的程式碼
import json
from itemadapter import ItemAdapter
# 如果想要使用管道的話,那麼就必須在settings中開啟管道
class DangdangPipeline:
# 在爬蟲開始之前執行的方法
def open_spider(self, spider):
# 初始化一個空列表,用來儲存爬取的資料
self.data_list = []
# 處理每個爬取到的資料項
def process_item(self, item, spider):
# 將每個資料項轉換成字典格式
data = dict(item)
# 將資料項新增到列表中
self.data_list.append(data)
# 返回item,這樣其他的pipeline元件(如果有的話)也能處理該item
return item
# 在爬蟲執行完畢後執行的方法
def close_spider(self, spider):
# 將爬取到的資料列表寫入到JSON檔案中
with open('book.json', 'wt', encoding='utf-8') as file:
json.dump(self.data_list, file, ensure_ascii=False)
# settings.py
# 管道預設沒有開啟
# 開啟管道,管道是有優先順序的,優先順序的返回是1-1000 數字越低 優先順序越高 預設是300
ITEM_PIPELINES = {
"dangdang.pipelines.DangdangPipeline": 300,
}
開啟多條管道 (模仿就行)
不同管道可以執行不同的任務,比如你儲存資料,我下載圖片到本地。
# 1. 在pipelines.py中 模仿預設的類去寫,類名隨意,裡面定義一樣的方法就行 process_item(self, item, spider).
# 例如
class ImageDownload:
def process_item(self, item, spider):
# 寫邏輯
return item
# 2. 在settings中,一樣模仿預設的去寫
ITEM_PIPELINES = {
"dangdang.pipelines.DangdangPipeline": 300, # 這個是預設的,DangdangPipeline是建立scrapy就建立好的類名
"dangdang.pipelines.ImageDownload": 301 # 這個是自己新增的,ImageDownload 就是你自己定義的類名
}
# 完畢
# 引入必要的庫
from pathlib import Path
import json
from urllib.request import urlretrieve
from itemadapter import ItemAdapter
# 設定儲存圖片的資料夾路徑
BASE_DIR = Path(__file__).parent
path = BASE_DIR / 'images'
path.mkdir(parents=True, exist_ok=True)
# 定義第一個管道類,用於處理資料儲存到 JSON 檔案中
class DangdangPipeline:
# 在爬蟲開始之前執行的方法
def open_spider(self, spider):
# 初始化一個空列表,用於儲存爬取的資料
self.data_list = []
# 處理每個爬取到的資料項
def process_item(self, item, spider):
# 將每個資料項轉換成字典格式
data = dict(item)
# 列印圖片連結
print(item.get('src'))
# 將資料項新增到列表中
self.data_list.append(data)
# 返回 item,這樣其他的 pipeline 元件(如果有的話)也能處理該 item
return item
# 在爬蟲執行完畢後執行的方法
def close_spider(self, spider):
# 將爬取到的資料列表寫入到 JSON 檔案中
with open('book.json', 'wt', encoding='utf-8') as file:
json.dump(self.data_list, file, ensure_ascii=False)
# 定義第二個管道類,用於下載圖片
# "dangdang.pipelines.ImageDownload": 301
class ImageDownload:
def process_item(self, item, spider):
# 獲取書籍名稱和圖片連結
name = item.get('name')
src = f"http:{item.get('src')}"
# 使用 urllib 庫下載圖片,並儲存到指定路徑下
urlretrieve(src, filename=f'{path}/{name}.png')
# 返回 item,以便其他 pipeline 元件(如果有的話)繼續處理該 item
return item
使用非同步下載
import aiohttp
import aiofiles
import asyncio
import os
from itemadapter import ItemAdapter
class ImageDownload:
def __init__(self):
self.session = None
async def download_image(self, url, filename):
# 使用 aiohttp 庫傳送非同步請求下載圖片
async with self.session.get(url) as response:
# 如果響應狀態為 200,則表示請求成功
if response.status == 200:
# 使用 aiofiles 庫非同步寫入檔案
async with aiofiles.open(filename, 'wb') as f:
await f.write(await response.read())
async def process_item(self, item, spider):
# 獲取書籍名稱和圖片連結
name = item.get('name')
src = f"http:{item.get('src')}"
# 拼接圖片檔案路徑
filename = os.path.join(path, f"{name}.png")
# 建立 aiohttp 的非同步會話
async with aiohttp.ClientSession() as session:
self.session = session
# 呼叫非同步下載圖片的方法
await self.download_image(src, filename)
return item
多頁下載
清楚一件事情:每一頁的業務邏輯都是完全一樣的,只需要開啟多頁就可以了,怎麼開啟?
- 我們只需要將執行頁的那個請求,再次呼叫parse方法就可以了
# spider\your_spider.py
import scrapy
from ..items import DangdangItem
class BookSpider(scrapy.Spider):
name = "book"
# 如果是多頁下載,那麼必須要調整allowed_domains的範圍,一般只寫域名
allowed_domains = ["xx"]
start_urls = ["xx"]
base_url = "xx"
page = 1
def parse(self, response):
# src = '//ul[@id="component_59"]//li/a/img/@src'
# title = '//ul[@id="component_59"]//li/a/img/@alt'
# price = '//ul[@id="component_59"]//li/p[@class="price"]/span[1]/text()'
li_list = response.xpath('//ul[@id="component_59"]//li')
for li in li_list:
src = li.xpath('./a/img/@data-original').extract_first()
name = li.xpath('./a/img/@alt').extract_first()
price = li.xpath('./p[@class="price"]/span[1]/text()').extract_first()
# print(src, name, price)
# 這個物件要交給管道pipelines去下載
book = DangdangItem(src=src, name=name, price=price)
# 獲取一個book 就將book交給pipelines
yield book
if self.page < 100:
self.page += 1
url = f"{self.base_url}{self.page}-cp01.01.02.00.00.00.html"
# 怎麼去呼叫parse方法
# yield Request 這個就是scrapy的get請求
# url就是請求的地址 callback就是要執行的函式,放記憶體地址就行,不需要加括號
yield scrapy.Request(url, callback=self.parse)
meta的使用
在 Scrapy 中,yield scrapy.Request
方法用於傳送HTTP請求,並且可以傳遞額外的後設資料(metadata)資訊給回撥函式。這些額外的後設資料可以在後續處理中使用。
具體來說,meta
引數是一個字典,可以包含任意鍵值對的資訊。當你使用 yield scrapy.Request(url, callback=self.parse_detail, meta={'key': 'value'})
傳送請求時,你可以在 parse_detail
回撥函式中透過 response.meta['key']
來獲取傳遞的值。
例如,假設你有一個爬蟲,爬取網頁中的標題和連結,並且想要將標題和連結一起儲存到資料庫中。你可以使用 meta
引數將標題傳遞給下一個回撥函式。示例程式碼如下:
import scrapy
class MySpider(scrapy.Spider):
name = 'myspider'
def start_requests(self):
urls = ['https://example.com/page1', 'https://example.com/page2']
for url in urls:
yield scrapy.Request(url, callback=self.parse_title)
def parse_title(self, response):
title = response.css('h1::text').get()
yield scrapy.Request(response.url, callback=self.parse_detail, meta={'title': title})
def parse_detail(self, response):
title = response.meta['title']
link = response.url
# Save title and link to database or do other processing
在上面的示例中,parse_title
方法提取頁面的標題,並將標題作為後設資料傳遞給 parse_detail
方法。在 parse_detail
方法中,可以透過 response.meta['title']
獲取到傳遞過來的標題資訊。
這樣,透過使用 meta
引數,可以方便地在 Scrapy 中傳遞和共享資料資訊。
連線提取器 CrawlSpider
1. 繼承自scrapy.Spider
2. 獨門秘籍
- crawlSpider可以自定義規則,在解析HTML內容的時候,可以根據連線規則提取出指定的連線,然後再向這些連線傳送請求。
- 比如,不知道一共有多少頁,就可以使用crawlspider
- 所以,如果有需要跟進連線的需求,意思就是爬取了網頁之後,需要提取連線再次爬取,使用crawlSpider是非常合適的。
3. 提取連結
連結提取器,在這裡就可以寫規則提取指定連線
scrapy.linkextractors.LinkExtractor(
allow=(), # 正規表示式,提取符合正則規則的連結
deny=(), # (不用)正規表示式,不提取符合正則規則的連結
allow_domains=(), # (不用)允許的域名
deny_domains=(), # (不用)不允許的域名
restrict_xpaths=(), # XPath,提取符合XPath規則的連結
restrict_css=() # CSS選擇器,提取符合CSS選擇器規則的連結
)
4. 模擬使用
from scrapy.linkextractors import LinkExtractor # 匯入必要的模組
正則用法:links1 = linkExtractor(allow='list_23_\d+\.html')
xpath用法:links2 = linkExtractor(restrict_xpaths=r'//div[@class="x"]') # 這裡記得是xpaths不是xpath
css用法:links3 = LinkExtractor(restrict_css=".x")
5. 提取連線
link.extract_links(response) # link1.extract_links(response) ...
6. 注意事項
【注意1】callback只能寫函式名字串,callback="parse_item"
【注意2】在基本的spider中,如果重新傳送請求,那裡的callback寫的是 callback=self.parse_item
fllow=true # 是否更進,就是按照提取連線規則進行提取
fllow=True
CrawlSpider
中的follow=True
參數列示是否在爬取頁面時遵循定義的規則(Rule)。如果follow=True
,則爬蟲會根據規則繼續跟進頁面,解析頁面中的連結,並且對這些連結再次進行爬取。換句話說,它會讓爬蟲繼續向下深入爬取新的頁面。
通常情況下,當follow=True
時,爬蟲會繼續爬取滿足規則條件的連結,直到滿足某種條件(比如達到指定的頁數、深度等)或者沒有新的連結可爬取為止。因此,這個引數可以用來控制爬蟲的深度和範圍。
總之,follow=True
會使爬蟲按照定義的規則繼續爬取新的頁面,直到滿足指定條件為止。
如何使用crawlspider
1. cd到某個目錄,然後正常建立爬蟲專案
- scrapy startproject 專案的名字
2. 跳轉到spiders資料夾的目錄下
- cd專案名字\專案名字\spiders
3. 建立爬蟲檔案 (區別於一般的建立 --> scrapy genspider 爬蟲的名字 爬取的域名)
- 要使用下面的建立語法
- scrapy genspider -t crawl 爬蟲檔案的名字 爬取的域名
# 使用crawlspider建立後的spider
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
class ReadSpider(CrawlSpider):
name = "read"
allowed_domains = ["www.xx.com"]
start_urls = ["https://www.xx.com/book/1188_1.html"]
rules = (Rule(LinkExtractor(allow=r"Items/"), callback="parse_item", follow=True),)
def parse_item(self, response):
item = {}
# 下面幾個註釋的是建立後就自動生成的,我們不適用這種,刪除就行。
#item["domain_id"] = response.xpath('//input[@id="sid"]/@value').get()
#item["name"] = response.xpath('//div[@id="name"]').get()
#item["description"] = response.xpath('//div[@id="description"]').get()
return item
寫入json檔案
# spider\read.py
import scrapy
from ..items import ReadbookItem
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
class ReadSpider(CrawlSpider):
name = "read"
allowed_domains = ["www.xx.com"]
start_urls = ["https://www.xx.com/book/1188_1.html"]
rules = (Rule(LinkExtractor(
allow=r"/book/1188_\d+\.html"),
callback="parse_item", # callback只能寫函式名字串,callback="parse_item"
follow=True),)
def parse_item(self, response):
data_list = response.xpath("//div[@class='book-info']/div/a/img")
for line in data_list:
name = line.xpath("./@alt").extract_first()
src = line.xpath("./@data-original").extract_first()
print(name, src)
book = ReadbookItem(name=name, src=src)
yield book
# items.py
import scrapy
class ReadbookItem(scrapy.Item):
name = scrapy.Field()
src = scrapy.Field()
# pipelines.py
import json
from itemadapter import ItemAdapter
class ReadbookPipeline:
def open_spider(self, spider):
# 在爬蟲啟動時初始化一個空列表用來儲存資料
self.data_list = []
def process_item(self, item, spider):
# 將Item物件轉換為字典格式並新增到資料列表中
data = dict(item)
self.data_list.append(data)
return item # 返回Item物件
def close_spider(self, spider):
# 當爬蟲關閉時,將資料列表寫入到JSON檔案中
with open('book.json', 'wt', encoding='utf-8') as file:
json.dump(self.data_list, file, ensure_ascii=False)
寫入資料庫 sqlalchemy
注意!commit一定要拿到一個資料就提交一次!!
資料庫設定
# settings.py 中任意位置新增
DB_HOST = "localhost" # 資料庫主機地址 localhost同107.0.0.1
DB_PORT = 3306 # 資料庫埠號
DB_USER = "root" # 資料庫使用者名稱
DB_PASSWORD = "123456" # 資料庫密碼
DB_NAME = "spider1" # 資料庫名稱
DB_CHARSET = 'utf8' # 資料庫字符集設定 這裡不能加橫槓 -
載入settings檔案
如果用得到,因為我使用的是sqlalchemy,所以壓根就不需要設定settings
from scrapy.utils.project import get_project_settings
核心程式碼
# items.py
import scrapy
class ReadbookItem(scrapy.Item):
# 定義您的資料項欄位在這裡,例如:
name = scrapy.Field() # 書名
src = scrapy.Field() # 連結地址
# spiders\read.py
import scrapy
from ..items import ReadbookItem # 匯入自定義的資料項類 ReadbookItem
from scrapy.linkextractors import LinkExtractor # 匯入 LinkExtractor 類
from scrapy.spiders import CrawlSpider, Rule # 匯入 CrawlSpider 類和 Rule 類
class ReadSpider(CrawlSpider):
name = "read" # 爬蟲的名稱
allowed_domains = ["www.xx.com"] # 允許爬取的域名
start_urls = ["https://www.xx.com/book/1188_1.html"] # 爬蟲啟動的起始連結
rules = (Rule(LinkExtractor(
allow=r"/book/1188_\d+\.html"), # 定義規則,允許匹配的連結格式
callback="parse_item", # 指定回撥函式為 parse_item 這裡只能給字串
follow=False),) # 不跟蹤提取到的連結 ,如果要跟蹤爬取設定為True即可,一般設定為True
def parse_item(self, response):
data_list = response.xpath("//div[@class='book-info']/div/a/img") # 使用 XPath 選擇器提取書籍資訊
for line in data_list:
name = line.xpath("./@alt").extract_first() # 提取書名
src = line.xpath("./@data-original").extract_first() # 提取連結地址
print(name, src) # 列印提取到的書名和連結地址
book = ReadbookItem(name=name, src=src) # 建立一個 ReadbookItem 例項
yield book # 返回提取的資料項
import scrapy
from ..items import ReadbookItem
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
class ReadSpider(CrawlSpider):
name = "read"
allowed_domains = ["www.dushu.com"]
start_urls = ["https://www.dushu.com/book/1188_1.html"]
rules = (Rule(LinkExtractor(
allow=r"/book/1188_\d+\.html"),
callback="parse_item", # callback只能寫函式名字串,callback="parse_item"
follow=False),)
def parse_item(self, response):
data_list = response.xpath("//div[@class='book-info']/div/a/img")
for line in data_list:
name = line.xpath("./@alt").extract_first()
src = line.xpath("./@data-original").extract_first()
print(name, src)
book = ReadbookItem(name=name, src=src)
yield book #
# pipelines.py
import json
import sqlalchemy
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
from itemadapter import ItemAdapter
# 建立資料庫引擎
engine = sqlalchemy.create_engine("mysql://root:243204@localhost/spider1?charset=utf8")
# 建立基礎模型類
Base = declarative_base()
# 定義書籍類
class ReadBook(Base):
__tablename__ = "readbook"
id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True, autoincrement=True)
name = sqlalchemy.Column(sqlalchemy.String(128), nullable=True)
src = sqlalchemy.Column(sqlalchemy.String(255), nullable=True)
# 建立所有的表結構
Base.metadata.create_all(engine)
# 建立資料庫會話
Session = sessionmaker(bind=engine)
class ReadbookPipeline:
def open_spider(self, spider):
self.data_list = []
def process_item(self, item, spider):
data = dict(item)
self.data_list.append(data)
return item
def close_spider(self, spider):
# 將資料寫入 JSON 檔案
with open('book.json', 'wt', encoding='utf-8') as file:
json.dump(self.data_list, file, ensure_ascii=False)
class MysqlPipeline:
def open_spider(self, spider):
# 在爬蟲啟動時建立資料庫會話
self.session = Session()
def process_item(self, item, spider):
# 處理每個條目並將其新增到資料庫會話中
book = ReadBook(
name=item['name'],
src=item['src']
)
self.session.add(book)
# 每次處理完一個條目就提交一次
self.session.commit()
def close_spider(self, spider):
# 在爬蟲關閉時提交事務並關閉資料庫會話
self.session.commit()
self.session.close() # 關閉資料庫會話
日誌資訊和等級
1. 日誌級別
- CRITICAL: 嚴重錯誤
- ERROR: 一般錯誤
- WARNING: 警告
- INFO: 一般資訊
- DEBUG: 除錯資訊
預設的日誌等級是DEBUG
只要出現了DEBUG或者DEBUG以上等級的日誌
那麼這些日誌就會列印
2. settings.py檔案設定
預設的級別為DEBUG,會顯示上面所有的資訊
在配置檔案中 settings.py
LOG_FILE:將螢幕顯示的資訊全部記錄到檔案中,螢幕不再顯示,注意檔案後續一定是.log
LOG_LEVEL: 設定日誌顯示的等級,就是顯示哪些,不顯示哪些
eg:
- LOG_FILE = "logdemo.log"
- LOG_LEVEL = "LEVEL" # 一般不會設定等級, 而是寫入到檔案裡面,方便查錯,定義此項後,後續的日誌記錄就會追加到這個日誌裡面
- 執行語句後面新增 --nolog
- scrapy crawl xx --nolog
【*】 推薦使用LOG_FILE 的方式
scrapy的post請求
# 1. 重寫start_requests方法
def start_requests(self)
# 2. start_requests的返回值
scrapy.FromFequest(url=url, headers=headers, callback=self.parse_item, formdata=data)
url: 要傳送的post地址
headers: 可以定製頭資訊
callback: 回撥函式
formdata: post所攜帶的資料,這是一個字典
import json
import scrapy
class TestpostSpider(scrapy.Spider):
name = "testpost"
allowed_domains = ["fanyi.baidu.com"]
# post請求 如果不寫任何引數,那麼寫過請求將會沒有任何的意義
# 所以 這個start_urls也沒有用了
# 由於parse依賴於start_urls 所以 parse也沒有用了
# start_urls = ["https://fanyi.baidu.com/sug"]
# def parse(self, response):
# pass
def start_requests(self):
url = "https://fanyi.baidu.com/sug"
data = {"kw": "cancer"}
yield scrapy.FormRequest(url=url, formdata=data, callback=self.parse_second)
def parse_second(self, response):
content = response.body
data = json.loads(content)
print(data)
爬蟲中介軟體和下載中介軟體
爬蟲中介軟體
# 爬蟲中介軟體
class Day06StartSpiderMiddleware:
# Not all methods need to be defined. If a method is not defined,
# scrapy acts as if the spider middleware does not modify the
# passed objects.
@classmethod
def from_crawler(cls, crawler):
# 該方法是Scrapy用於建立爬蟲例項的方法。
# This method is used by Scrapy to create your spiders.
s = cls()
crawler.signals.connect(s.spider_opened, signal=signals.spider_opened)
return s
def process_spider_input(self, response, spider):
# 當響應從爬蟲中介軟體進入爬蟲時,呼叫該方法進行處理。
# 應返回None或引發異常。
# Called for each response that goes through the spider
# middleware and into the spider.
# Should return None or raise an exception.
return None
def process_spider_output(self, response, result, spider):
# 當爬蟲處理完響應後,呼叫該方法對處理結果進行處理。
# 必須返回一個可迭代的Request物件或item物件。
# Called with the results returned from the Spider, after
# it has processed the response.
# Must return an iterable of Request, or item objects.
for i in result:
yield i
def process_spider_exception(self, response, exception, spider):
# 當爬蟲中丟擲異常時,呼叫該方法進行處理。
# 應返回None或者一個可迭代的Request物件或item物件。
# Called when a spider or process_spider_input() method
# (from other spider middleware) raises an exception.
# Should return either None or an iterable of Request or item objects.
pass
def process_start_requests(self, start_requests, spider):
# 在爬蟲啟動時,對初始請求進行處理。
# Called with the start requests of the spider, and works
# similarly to the process_spider_output() method, except
# that it doesn’t have a response associated.
# Must return only requests (not items).
for r in start_requests:
yield r
def spider_opened(self, spider):
spider.logger.info("Spider opened: %s" % spider.name)
這段程式碼是一個爬蟲中介軟體,用於在Scrapy框架中對爬蟲進行處理。下面是對每個方法的功能和用途的詳細解釋:
class Day06StartSpiderMiddleware: # Not all methods need to be defined. If a method is not defined, # scrapy acts as if the spider middleware does not modify the # passed objects. @classmethod def from_crawler(cls, crawler): # 該方法是Scrapy用於建立爬蟲例項的方法。 s = cls() crawler.signals.connect(s.spider_opened, signal=signals.spider_opened) return s
from_crawler
方法是一個類方法,它會在建立爬蟲例項時被Scrapy呼叫,用於初始化爬蟲中介軟體的例項。在該方法中,首先建立了一箇中介軟體例項s
,然後透過crawler.signals.connect
方法連線了spider_opened
訊號和對應的處理方法。def process_spider_input(self, response, spider): # 當響應從爬蟲中介軟體進入爬蟲時,呼叫該方法進行處理。 # 應返回None或引發異常。 return None
process_spider_input
方法會在響應從爬蟲中介軟體傳遞到爬蟲之前呼叫。它接收兩個引數:response
是響應物件,spider
是當前爬蟲例項。這個方法可以用來對響應進行預處理或檢查。應該返回None
或引發異常。def process_spider_output(self, response, result, spider): # 當爬蟲處理完響應後,呼叫該方法對處理結果進行處理。 # 必須返回一個可迭代的Request物件或item物件。 for i in result: yield i
process_spider_output
方法在爬蟲處理完響應後會被呼叫。它接收三個引數:response
是爬蟲處理後的響應物件,result
是爬蟲的處理結果,spider
是當前爬蟲例項。這個方法主要用於對爬蟲處理結果進行進一步處理或過濾,並將處理結果返回。必須返回一個可迭代的Request物件或item物件。def process_spider_exception(self, response, exception, spider): # 當爬蟲中丟擲異常時,呼叫該方法進行處理。 # 應返回None或者一個可迭代的Request物件或item物件。 pass
process_spider_exception
方法在爬蟲或process_spider_input()
方法中丟擲異常時會被呼叫。它接收三個引數:response
是發生異常的響應物件,exception
是丟擲的異常物件,spider
是當前爬蟲例項。這個方法可以用來對爬蟲處理過程中的異常進行處理,可以返回None
或一個可迭代的Request物件或item物件。def process_start_requests(self, start_requests, spider): # 在爬蟲啟動時,對初始請求進行處理。 # Must return only requests (not items). for r in start_requests: yield r
process_start_requests
方法在爬蟲啟動時被呼叫,用於對初始請求進行處理。它接收兩個引數:start_requests
是初始請求的列表,spider
是當前爬蟲例項。這個方法必須返回一個可迭代的Request物件,而不能返回item物件。def spider_opened(self, spider): spider.logger.info("Spider opened: %s" % spider.name)
spider_opened
方法在爬蟲開啟時被呼叫。它接收一個引數spider
,表示當前爬蟲例項。在這個方法中,透過日誌記錄器(logger
)輸出 "Spider opened: 爬蟲名稱" 的資訊。
下載中介軟體
# 下載中介軟體
class Day06StartDownloaderMiddleware:
# 不是所有的方法都需要定義。如果某個方法沒有被定義,
# Scrapy會認為這個下載中介軟體不會修改傳遞的物件。
@classmethod
def from_crawler(cls, crawler):
# Scrapy使用該方法建立您的爬蟲。
s = cls()
# 透過signals連線spider_opened訊號和spider_opened方法
crawler.signals.connect(s.spider_opened, signal=signals.spider_opened)
return s
# 攔截處理所有的請求物件
# 引數:request就是攔截到的請求物件,spider爬蟲檔案中爬蟲類例項化的物件
# spider引數的作用可以實現爬蟲類和中間類的資料互動
def process_request(self, request, spider):
# 返回None:繼續處理本次請求,執行下一個中介軟體的process_request方法
# 返回一個Response物件:執行當前中介軟體的process_response方法,重新回到引擎,被排程
# 返回一個Request物件:直接返回給引擎,被排程。進入排程器等待下次被呼叫
# 丟擲IgnoreRequest異常:呼叫已安裝的下載中介軟體的process_exception方法
return None
# 攔截處理所有的響應物件
# 引數:response就是攔截到的響應物件,request就是被攔截到響應物件對應的唯一的一個請求物件
def process_response(self, request, response, spider):
# - 返回一個Response物件:繼續執行,進入引擎,被排程到爬蟲進行解析
# - 返回一個Request物件:進入引擎,返回到排程器被重新呼叫
# - 或者丟擲IgnoreRequest異常:丟擲異常
return response
# 攔截和處理發生異常的請求物件
# 引數:reqeust就是攔截到的發生異常的請求物件
# 方法存在的意義:將發生異常的請求攔截到,然後對其進行修正
def process_exception(self, request, exception, spider):
# 當下載處理程式或process_request()方法(來自其他下載中介軟體)引發異常時呼叫。
# 必須返回以下之一:
# - 返回None:繼續處理該異常
# - 返回一個Response物件:停止process_exception()鏈
# - 返回一個Request物件:停止process_exception()鏈
pass
# 控制日誌資料的(忽略)
def spider_opened(self, spider):
spider.logger.info("Spider opened: %s" % spider.name)
from_crawler(cls, crawler)
方法:該方法是Scrapy用來建立爬蟲的入口點。它返回一箇中介軟體物件,並透過crawler.signals
連線到spider_opened
訊號,以便在爬蟲開啟時執行相應的操作。process_request(self, request, spider)
方法:該方法在傳送請求之前被呼叫。您可以在此方法中對請求進行處理和修改。返回值決定了後續處理的行為,可以是None
繼續處理當前請求,返回一個Response
物件以便執行process_response
方法,返回一個Request
物件以便重新排程,或者丟擲IgnoreRequest
異常以呼叫其他下載中介軟體的process_exception
方法。process_response(self, request, response, spider)
方法:該方法在收到響應後被呼叫。您可以在此方法中對響應進行處理和修改。返回值決定了後續處理的行為,可以是返回Response
物件以便進一步處理和解析,返回Request
物件以便重新排程,或者丟擲IgnoreRequest
異常。process_exception(self, request, exception, spider)
方法:當下載處理程式或其他下載中介軟體的process_request
方法引發異常時呼叫該方法。您可以在此處處理異常,並根據需要返回值。spider_opened(self, spider)
方法:在爬蟲開啟時被呼叫。在這個示例中,它會輸出一個日誌資訊。
配置檔案
# Enable or disable spider middlewares
# See https://docs.scrapy.org/en/latest/topics/spider-middleware.html
SPIDER_MIDDLEWARES = {
"day06Start.middlewares.Day06StartSpiderMiddleware": 543,
}
# Enable or disable downloader middlewares
# See https://docs.scrapy.org/en/latest/topics/downloader-middleware.html
DOWNLOADER_MIDDLEWARES = {
"day06Start.middlewares.Day06StartDownloaderMiddleware": 543,
請求頭相關
修改請求頭
在下載中介軟體的def process_request(self, request, spider):寫程式碼
加代理
- 在下載中介軟體寫process_request方法
def get_proxy(self):
import requests
res = requests.get('http://127.0.0.1:5010/get/').json()
if res.get('https'):
return 'https://' + res.get('proxy')
else:
return 'http://' + res.get('proxy')
def process_request(self, request, spider):
request.meta['proxy'] = self.get_proxy()
return None
- 代理可能不能用,會觸發process_exception,在裡面寫
def process_exception(self, request, exception, spider):
# 第二步:代理可能不能用,會觸發process_exception,在裡面寫
def process_exception(self, request, exception, spider):
print('-----', request.url) # 這個地址沒有爬
return request
加cookies
def process_request(self, request, spider):
# 新增cookie
request.cookies['cookies'] = 'cookies'
print(request.url+':請求物件攔截成功!')
return None
修改請求頭
def process_request(self, request, spider):
request.headers['referer'] = 'http://www.lagou.com'
return None
隨機UA
# 動態生成User-agent使用
def process_request(self, request, spider):
# fake_useragent模組
from fake_useragent import UserAgent
request.headers['User-Agent']=str(UserAgent().random)
pprint(request.url+':請求物件攔截成功!')
return None
小結
def process_request(self, request, spider):
# 返回None:繼續處理本次請求,執行下一個中介軟體的process_request方法
# 返回一個Response物件:執行當前中介軟體的process_response方法,重新回到引擎,被排程
# 返回一個Request物件:直接返回給引擎,被排程。進入排程器等待下次被呼叫
# 丟擲IgnoreRequest異常:呼叫已安裝的下載中介軟體的process_exception方法
# 構建代理池
# 第一步:構建代理池
def get_proxy(self):
import requests
res = requests.get('http://127.0.0.1:5010/get/').json()
if res.get('https'):
return 'https://' + res.get('proxy')
else:
return 'http://' + res.get('proxy')
def process_request(self, request, spider):
# 在 meta 中加入代理
request.meta['proxy'] = self.get_proxy()
# 代理可能不能用,會觸發process_exception,在裡面寫
return None
# 新增cookie
request.cookies['cookies'] = 'cookies'
# 修改請求頭
request.headers['referer'] = 'http://www.lagou.com'
# 動態生成User-agent使用
request.headers['User-Agent'] = str(UserAgent().random)
return None
def process_exception(self, request, exception, spider):
# 第二步:代理可能不能用,會觸發process_exception,在裡面寫
def process_exception(self, request, exception, spider):
print('-----', request.url) # 這個地址沒有爬
return request