養成好習慣,點個贊 再走;有問題,歡迎私信、評論,我看到都會回覆的
- 爬蟲除了會Python,還需要掌握HTML、CSS,注意:不需要精通HTML、CSS,只要能看懂頁面結構
- 即:明白HTML怎麼構成頁面結構、CSS選擇器這些知識,和掌握使用瀏覽器中檢查代碼即可
相關文章推薦:
scrapy:是一個應用程序框架,用於對網站進行爬行和提取結構化數據,這些結構化數據可用於各種有用的應用程序,如數據挖掘、信息處理或歷史存檔。
其實學習一個工具的使用方法的最好辦法是閱讀官方文檔,但是很遺憾Scrapy是外國出品,顯而易見語言爲英文,閱讀起來較爲勞累。
雖然官方文檔也有中文版的,但是他喵的滿滿的機翻味,細節方面太差了,專業名詞、代碼翻譯過來反而看不懂了,語序顛倒的問題也十分嚴重
遂打算通過苦啃官方文檔,將關鍵信息記錄下來,分享的同時也方便日後快速查閱
文章目錄
目錄一:一個簡單的Scrapy Spider示例
第一步:分析頁面結構
http://quotes.toscrape.com/,是一個可以練習爬蟲的網站
- 這個頁面結構十分簡單,內容主體由 quote 組成,以卡片式展示;頁面右邊展示的是10個常用標籤子頁面的url,暫時不用管它
- 每個 quote 由三部分組成:文本、作者和若干個標籤
- 文本
- 作者
- 標籤
- 文本
- 一個頁面能承載的信息是有限的,需要分頁展示,每個頁面的底端有一個 下一頁按鈕
第二步:寫代碼
示例代碼搬運自官方文檔 >.<
官方文檔中提供的代碼爬取的就是這個網站的子頁面 Viewing tag: humor,其url爲http://quotes.toscrape.com/tag/humor/,頁面結構還是我上面所說的
import scrapy
class QuotesSpider(scrapy.Spider):
name = 'quotes'
start_urls = [
'http://quotes.toscrape.com/tag/humor/',
]
def parse(self, response):
for quote in response.css('div.quote'):
yield {
'author': quote.xpath('span/small/text()').get(),
'text': quote.css('span.text::text').get(),
}
next_page = response.css('li.next a::attr("href")').get()
if next_page is not None:
yield response.follow(next_page, self.parse)
假設我將上面的代碼寫在quotes_spider.py
代碼中,我們不能按照一般情況下來運行這個python文件:python quotes_spider.py
雖然scrapy是python第三方包,但也是一個框架,我們需要按照他給定的規則來運行
- 正確的運行方式
scrapy runspider quotes_spider.py -o quotes.json
一:運行時
在代碼的運行過程中,會在終端輸出許多信息:可以看作三部分
- 第一部分:輸出的是Scrapy及其依賴包的信息
- 第二部分:輸出的是爬取的內容
- 第三部分:輸出的是對爬取信息的一個總結 ,形如下圖
二:運行完成後
運行完成之後,Scrapy Spider會將爬取的內容寫入quotes.json文件(它會被創建在quotes_spider.py文件路徑下)
Viewing tag: humor 下每個 quote 的作者、文本以字典形式存儲,若干個字典存放在列表中
第三步:分析代碼
- 當我們輸入
scrapy runspider quotes_spider.py
命令後,Scrapy 通過crawler engine運行代碼 - crawler 對 start_urls 屬性中定義的URL發出請求,並調用默認的回調方法 parse()
通俗來說:
這個 parse() 方法通常解析response,將抓取的數據提取爲dict,並查找新的URL以跟蹤和創建新的請求
按照本代碼來說:
def parse(self, response):
for quote in response.css('div.quote'):
yield {
'author': quote.xpath('span/small/text()').get(),
'text': quote.css('span.text::text').get(),
}
next_page = response.css('li.next a::attr("href")').get()
if next_page is not None:
yield response.follow(next_page, self.parse)
- 使用CSS選擇器循環 div.quote 元素,然後使用css選擇器或者xpath生成一個包含 quote的作者、文本的列表
css選擇器的使用無縫對接css中我們對選擇器的認知
xpath是官方推薦我們使用的 - 查找到下一頁的鏈接,並使用它繼續調度 parse() 方法
目錄二:Scrapy Spider
好了,通過目錄一,我想你應該對Scrapy有了一個直觀的印象
教程正式開始
第一步:創建一個新的Scrapy項目
xxx是存儲代碼並運行的目錄(文件夾)及其下內容
scrapy startproject xxx
會生成該目錄(我輸入的目錄名爲zgh)
並且通過它給的信息
可知,這個目錄及其下內容是通過Spider中的模板生成的
目錄結構:
xxx/
- scrapy.cfg,部署配置文件
- xxx/ ,Python項目模板,你將從這兒導入你的代碼
- __init__.py
- items.py,項目定義文件
- middlewares.py,項目中間件文件
- pipelines.py,項目管道文件
- settings.py,項目設置文件
- spiders/,你將在這兒存放你寫的Spider代碼
- __init__.py
第二步:定義繼承Spider的類並定義初始請求
示例代碼搬運自官方文檔 >.<
在xxx/xxx/spiders/ 路徑下存代碼文件,代碼中定義一個類,需要繼承 scrapy.Spider
import scrapy
class QuotesSpider(scrapy.Spider):
name = "quotes"
start_urls = [
'http://quotes.toscrape.com/page/1/',
'http://quotes.toscrape.com/page/2/',
]
def parse(self, response):
page = response.url.split("/")[-2] #根據上面的鏈接提取分頁,如:/page/1/,提取到的就是:1
filename = 'quotes-%s.html' % page #拼接文件名,如果是第一頁,最終文件名便是:newpage-1.html
with open(filename, 'wb') as f:
f.write(response.body)
self.log('Saved file %s' % filename)
還要定義屬性和方法
-
name :標識Spider。它在一個項目中必須是唯一的,也就是說,不能爲不同的Spider設置相同的名稱。
-
start_urls:具有URL列表的類屬性
-
parse() :將被調用來處理這些URL的每個請求。parse() 是Scrapy的默認回調方法,對沒有顯式分配回調的請求調用該方法。
parse() 方法通常解析響應,將抓取的數據提取爲dict,並查找新的URL以跟蹤和創建新的請求。
第三步:在項目中運行Spider
首先明確一點:上述Spider代碼中定義的name屬性爲:quotes
先轉到項目的頂層目錄(即第一步中創建的文件夾下),在終端中運行
scrapy crawl quotes
終端會輸出許多信息,信息中間可以看到
-
上述代碼只是將兩個URL的頁面源代碼爬取下來,並分別存放在quotes-1.html、quotes-1.html文件中(文件在項目的頂層目錄下)
-
上述代碼parse()方法並沒有解析HTML哦,先看目錄三學習怎麼在Scrapy中提取數據,然後目錄四觀看提取數據的示例
題外話:很有意思的一點,在截圖的第四行中可以看到這個網站並沒有在網站根目錄下定義爬蟲協議robots.txt
目錄三:Scrapy Shell
學習使用scrappy提取數據的最佳方法是使用 Scrapy shell
在終端中輸入
scrapy shell url
- url:輸入網頁url字符串,在Windows中請使用雙引號!
比如,我輸入的url爲:"http://quotes.toscrape.com/page/1/"
終端中會輸出一些信息
- 首先是:Scrapy log 信息(不用管它們)
- 然後是:返回狀態碼200,代表該網站爬取成功
- 最後給出:我們可以使用的Scrapy對象
- 最下面一行就是交互式命令行:Shell
在Shell中輸入view(response)
,就會在你電腦上默認的瀏覽器上打開該網頁(你上面輸入的url)
response.css()
這個方法無縫對接CSS選擇器,用來選擇頁面的元素
一:response.css()
>>> response.css('title')
[<Selector xpath='descendant-or-self::title' data='<title>Quotes to Scrape</title>'>]
>>> response.css('title::text')
[<Selector xpath='descendant-or-self::title/text()' data='Quotes to Scrape'>]
response.css('xxx')
返回的是一個類似列表的對象:SelectorList,- data中存儲的是提取到的標籤,
- xpath中存儲的XPath表達式,實際上CSS選擇器是在後臺轉換爲XPath表達式的
- 允許你進一步的細化選擇和提取數據
response.css('xxx::text')
與上面方法不同之處在於,data中存儲的是提取到的標籤的文本
二:response.css().getall()
>>> response.css('title').getall()
['<title>Quotes to Scrape</title>']
>>> response.css('title::text').getall()
['Quotes to Scrape']
response.css('xxx').getall()
、response.css('xxx::text').getall()
getall() 方法,返回的是一個列表,
列表中的值是 SelectorList 中的 data 值- 一般而言,選擇器返回的結果不止一個,getall() 方法 提取全部內容
三:response.css().get()
- 如果你只想要選擇器提取的結果中的第一個,你可以使用 get() 方法
>>> response.css('title').get()
'<title>Quotes to Scrape</title>'
>>> response.css('title::text').get()
'Quotes to Scrape'
- get() 方法,返回的是字符串
四:response.re()
Scrapy還支持 re() 方法,使用正則表達式來提取數據
response.xpath()
除了CSS,Scrapy選擇器還支持使用XPath表達式
>>> response.xpath('//title')
[<Selector xpath='//title' data='<title>Quotes to Scrape</title>'>]
>>> response.xpath('//title/text()')
[<Selector xpath='//title/text()' data='Quotes to Scrape'>]
>>> response.xpath('//title').get()
'<title>Quotes to Scrape</title>'
>>> response.xpath('//title/text()').get()
'Quotes to Scrape'
在Scrapy中,XPath表達式是Scrapy Selectors的基礎。實際上,CSS選擇器是在後臺轉換爲XPath的。如果你仔細閱讀 Shell 中 SelectorList 的文本表示形式。
如果你對XPath表達式感興趣,推薦閱讀:
- 官方文檔:using XPath with Scrapy Selectors here
- 官方文檔:this tutorial to learn XPath through examples
- 官方文檔:this tutorial to learn “how to think in XPath
目錄四:提取數據
演示
先在Shell中給你演示一下
scrapy shell "http://quotes.toscrape.com/"
(1)提取頁面內全部quote
>>> response.css("div.quote")
[<Selector xpath="descendant-or-self::div[@class and contains(concat(' ', normalize-space(@class), ' '), ' quote ')]" data='<div class="quote" itemscope itemtype...'>,
<Selector xpath="descendant-or-self::div[@class and contains(concat(' ', normalize-space(@class), ' '), ' quote ')]" data='<div class="quote" itemscope itemtype...'>,
...]
(2)獲取第一個quote的文本內容
每個 quote 由三部分組成:文本、作者和若干個標籤
- 文本
- 作者
- 標籤
>>> quote = response.css("div.quote")[0]
>>> text = quote.css("span.text::text").get()
>>> text
'“The world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.”'
>>> author = quote.css("small.author::text").get()
>>> author
'Albert Einstein'
>>> tags = quote.css("div.tags a.tag::text").getall()
>>> tags
['change', 'deep-thoughts', 'thinking', 'world']
(3)獲取下一頁url
- 需要抓取a標籤中href屬性值,有兩種方法
>>> response.css('li.next a').attrib['href']
'/page/2/'
>>> next = response.css('li.next a::attr(href)').get()
>>> next
'/page/2/'
- 注意,href 給的是相對地址,在使用的時候要結合網站根地址,拼接爲絕對地址
一般而言,我們可以這樣拼接字符串
>>> next = response.css('li.next a::attr(href)').get()
>>> next_page
'/page/2/'
>>> next = 'http://quotes.toscrape.c' + next
>>> next
'http://quotes.toscrape.c/page/2/'
幸運的是,Srapy提供了一個方法response.urljoin('url')
:將相對地址轉化爲絕對地址,而且也可以在其中輸入絕對地址哦
>>> next_page = response.css('li.next a::attr(href)').get()
>>> next_page
'/page/2/'
>>> response.urljoin(next_page)
'http://quotes.toscrape.com/page/2/'
抓取網站所有quote
import scrapy
class QuotesSpider(scrapy.Spider):
name = "quotes"
start_urls = [
'http://quotes.toscrape.com/page/1/',
]
def parse(self, response):
for quote in response.css('div.quote'):
yield {
'text': quote.css('span.text::text').get(),
'author': quote.css('small.author::text').get(),
'tags': quote.css('div.tags a.tag::text').getall(),
}
next_page = response.css('li.next a::attr(href)').get()
if next_page is not None:
next_page = response.urljoin(next_page)
yield scrapy.Request(next_page, callback = self.parse)
在項目頂層目錄下輸入scrapy crawl quotes
,運行該Spider代碼
在終端輸出的日誌中顯示爬取的信息:
2020-04-03 12:32:17 [scrapy.core.scraper] DEBUG: Scraped from <200 http://quotes.toscrape.com/page/1/>
{'text': '“The world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.”', 'author': 'Albert Einstein', 'tags': ['change', 'deep-thoughts', 'thinking', 'world']}
2020-04-03 12:32:17 [scrapy.core.scraper] DEBUG: Scraped from <200 http://quotes.toscrape.com/page/1/>
{'text': '“It is our choices, Harry, that show what we truly are, far more than our abilities.”', 'author': 'J.K. Rowling', 'tags': ['abilities', 'choices']}
...
目錄五:存儲抓取到的數據
JSON文件
在項目頂層目錄下輸入scrapy crawl quotes -o quotes.json
Scrapy 會生成quotes.json文件,並將爬取到的數據放到quotes.json文件中
注意:Scrapy有一個小缺陷,Scrapy往json文件(例如:quotes.json)中寫入信息時,是附加進去而不是覆蓋。二次寫入json文件會導致json文件格式混亂,所以你不能把一個非空json文件作爲輸出文件
JSON Lines文件
在項目頂層目錄下輸入scrapy crawl quotes -o quotes.jl
Scrapy 會生成quotes.jl文件,並將爬取到的數據放到quotes.jl文件中
JSON Lines格式像流一樣,所以你可以輕鬆地向其添加新記錄
目錄六:分頁查詢的額外寫法
在上文中,對於parse()方法中對於對於獲取下一頁鏈接,並遞歸訪問的代碼是這麼寫的:
next_page = response.css('li.next a::attr(href)').get()
if next_page is not None:
next_page = response.urljoin(next_page)
yield scrapy.Request(next_page, callback = self.parse)
第二種寫法:
- 與 scrapy.Request 不同,response.follow 直接支持相對URL
next_page = response.css('li.next a::attr(href)').get()
if next_page is not None:
yield response.follow(next_page, callback=self.parse)
第三種寫法:
- css選擇器提取到的數據直接傳給 response.follow
for href in response.css('ul.pager a::attr(href)'):
yield response.follow(href, callback=self.parse)
第四種寫法:
- response.follow 可以自動獲取a標籤中的href值
for a in response.css('ul.pager a'):
yield response.follow(a, callback=self.parse)
第五種寫法:
- yield from 語法可以拼接可迭代對象、自動處理異常,相比 yield 語法更加簡潔、好用
- 要結合 response.follow_all() 方法使用
anchors = response.css('ul.pager a')
yield from response.follow_all(anchors, callback=self.parse)
第六種寫法:
- 上一個寫法簡化
yield from response.follow_all(css='ul.pager a', callback=self.parse)
目錄七:最後,我們再來看一個例子吧
每一個quote的作者都指向一個鏈接
作者頁面形如此:
- 作者名
- 出生日期、地址
- 介紹
import scrapy
class AuthorSpider(scrapy.Spider):
name = 'author'
start_urls = ['http://quotes.toscrape.com/']
def parse(self, response):
author_page_links = response.css('.author + a')
yield from response.follow_all(author_page_links, self.parse_author)
pagination_links = response.css('li.next a')
yield from response.follow_all(pagination_links, self.parse)
def parse_author(self, response):
def extract_with_css(query):
return response.css(query).get(default='').strip()
yield {
'name': extract_with_css('h3.author-title::text'),
'birthdate': extract_with_css('.author-born-date::text'),
'birthlocation': extract_with_css('.author-born-location::text'),
'bio': extract_with_css('.author-description::text'),
}
即使同一位作者的quote很多,我們也不必擔心會多次訪問同一作者頁面。默認情況下,Scrapy會過濾重複的請求:已經訪問過的URL