# Scrapy 教程
> 譯者:[OSGeo 中國](https://www.osgeo.cn/)
在本教程中,我們假定scrapy已經安裝在您的系統上。如果還未安裝,看 [安裝指南](install.html#intro-install) .
我們將會爬 [quotes.toscrape.com](http://quotes.toscrape.com/)的網站, 這是一家列出著名作家的名言的網站。
本教程將指導您完成以下任務:
1. 創建新的Scrapy項目
2. 寫一篇 [spider](../topics/spiders.html#topics-spiders) 對網站進行爬網并提取數據
3. 使用命令行導出抓取的數據
4. 將spider改為遞歸跟蹤鏈接
5. 使用 Spider 參數
Scrapy是用 Python 寫的。如果您對這門語言不熟悉,您可能想從了解這門語言是什么開始,從 Scrapy 語言中得到最大的收獲。
如果您已經熟悉其他語言,并且希望快速學習Python,那么 [Python Tutorial](https://docs.python.org/3/tutorial) 是一種很好的資源。
如果您是編程新手,并且想從python開始,那么下面的書可能對您有用:
* [Automate the Boring Stuff With Python](https://automatetheboringstuff.com/)
* [How To Think Like a Computer Scientist](http://openbookproject.net/thinkcs/python/english3e/)
* [Learn Python 3 The Hard Way](https://learnpythonthehardway.org/python3/)
您也可以看看 [this list of Python resources for non-programmers](https://wiki.python.org/moin/BeginnersGuide/NonProgrammers) 以及 [suggested resources in the learnpython-subreddit](https://www.reddit.com/r/learnpython/wiki/index#wiki_new_to_python.3F) .
## 創建項目
在開始抓取之前,您必須建立一個新的Scrapy項目。首先,進入您想要儲存代碼的目錄,并運行以下代碼:
```py
scrapy startproject tutorial
```
這將創建一個 `tutorial` 目錄包含以下內容:
```py
tutorial/
scrapy.cfg # deploy configuration file
tutorial/ # project's Python module, you'll import your code from here
__init__.py
items.py # project items definition file
middlewares.py # project middlewares file
pipelines.py # project pipelines file
settings.py # project settings file
spiders/ # a directory where you'll later put your spiders
__init__.py
```
## 我們的第一只 Spider
Spider 是您定義的類,Scrapy用來從一個網站(或一組網站)爬取信息。它們必須是 `scrapy.Spider` 子類以及定義要發出的初始請求,可以選擇如何跟蹤頁面中的鏈接,以及如何解析下載的頁面內容以提取數據。
這是我們第一只 Spider 的代碼。將其保存在 `tutorial/spiders` 目錄下的 `quotes_spider.py` 文件里:
```py
import scrapy
class QuotesSpider(scrapy.Spider):
name = "quotes"
def start_requests(self):
urls = [
'http://quotes.toscrape.com/page/1/',
'http://quotes.toscrape.com/page/2/',
]
for url in urls:
yield scrapy.Request(url=url, callback=self.parse)
def parse(self, response):
page = response.url.split("/")[-2]
filename = 'quotes-%s.html' % page
with open(filename, 'wb') as f:
f.write(response.body)
self.log('Saved file %s' % filename)
```
如您所見,這是我們的 Spider [`scrapy.Spider`](../topics/spiders.html#scrapy.spiders.Spider "scrapy.spiders.Spider") 的子類。其屬性和方法的定義如下:
* [`name`](../topics/spiders.html#scrapy.spiders.Spider.name "scrapy.spiders.Spider.name") :Spider的標識 。它在一個項目中必須是唯一的,也就是說,您不能為不同的 Spider 設置相同的名稱。
* [`start_requests()`](../topics/spiders.html#scrapy.spiders.Spider.start_requests "scrapy.spiders.Spider.start_requests") :必須返回可迭代的請求(您可以返回一個請求列表或編寫一個生成器函數), Spider 將從中開始爬行。隨后的請求將從這些初始請求中依次生成。
* [`parse()`](../topics/spiders.html#scrapy.spiders.Spider.parse "scrapy.spiders.Spider.parse") :將調用的方法,用于處理為每個請求下載的響應。響應參數是 [`TextResponse`](../topics/request-response.html#scrapy.http.TextResponse "scrapy.http.TextResponse") 的實例,它保存頁面內容,并進一步處理它。
這個 [`parse()`](../topics/spiders.html#scrapy.spiders.Spider.parse "scrapy.spiders.Spider.parse") 方法通常解析響應,將抓取的數據提取為dict,并查找新的URL以跟蹤和創建新的請求。( [`Request`](../topics/request-response.html#scrapy.http.Request "scrapy.http.Request") 從他們那里。
### 如何運行我們的 Spider
要使 Spider 正常工作,請轉到項目的頂級目錄并運行:
```py
scrapy crawl quotes
```
此命令運行名為 `quotes` 的spider,這將發送一些請求至 `quotes.toscrape.com` 領域。您將得到類似于以下內容的輸出:
```py
... (omitted for brevity)
2016-12-16 21:24:05 [scrapy.core.engine] INFO: Spider opened
2016-12-16 21:24:05 [scrapy.extensions.logstats] INFO: Crawled 0 pages (at 0 pages/min), scraped 0 items (at 0 items/min)
2016-12-16 21:24:05 [scrapy.extensions.telnet] DEBUG: Telnet console listening on 127.0.0.1:6023
2016-12-16 21:24:05 [scrapy.core.engine] DEBUG: Crawled (404) <GET http://quotes.toscrape.com/robots.txt> (referer: None)
2016-12-16 21:24:05 [scrapy.core.engine] DEBUG: Crawled (200) <GET http://quotes.toscrape.com/page/1/> (referer: None)
2016-12-16 21:24:05 [scrapy.core.engine] DEBUG: Crawled (200) <GET http://quotes.toscrape.com/page/2/> (referer: None)
2016-12-16 21:24:05 [quotes] DEBUG: Saved file quotes-1.html
2016-12-16 21:24:05 [quotes] DEBUG: Saved file quotes-2.html
2016-12-16 21:24:05 [scrapy.core.engine] INFO: Closing spider (finished)
...
```
現在,檢查當前目錄中的文件。您應該注意有兩個的新文件已經被創建: _quotes-1.html_ 和 _quotes-2.html_。如 ` parse ` 方法所示,每個文件都含有URL。
注解
如果您想知道為什么我們還沒有解析HTML,請稍等,我們很快就會討論這個問題。
#### 實際上發生了什么事?
Scrapy 安排被 Spider 里 `start_requests` 方法所返回的對象,也就是 [`scrapy.Request`](../topics/request-response.html#scrapy.http.Request "scrapy.http.Request") 。在接收到每個響應時,它實例化 [`Response`](../topics/request-response.html#scrapy.http.Response "scrapy.http.Response") 對象并調用與請求關聯的回調方法(在本例中,為 `parse` 方法)將響應作為參數傳遞。
### 啟動請求方法的快捷方式
而不是執行 [`start_requests()`](../topics/spiders.html#scrapy.spiders.Spider.start_requests "scrapy.spiders.Spider.start_requests") 生成的方法 [`scrapy.Request`](../topics/request-response.html#scrapy.http.Request "scrapy.http.Request") 來自URL的對象,您只需定義 [`start_urls`](../topics/spiders.html#scrapy.spiders.Spider.start_urls "scrapy.spiders.Spider.start_urls") 具有URL列表的類屬性。然后,此列表將由 [`start_requests()`](../topics/spiders.html#scrapy.spiders.Spider.start_requests "scrapy.spiders.Spider.start_requests") 要為您的 Spider 創建初始請求,請執行以下操作:
```py
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]
filename = 'quotes-%s.html' % page
with open(filename, 'wb') as f:
f.write(response.body)
```
這個 [`parse()`](../topics/spiders.html#scrapy.spiders.Spider.parse "scrapy.spiders.Spider.parse") 方法將被調用來處理這些URL的每個請求,即使我們沒有明確地告訴Scrapy這樣做。這是因為 [`parse()`](../topics/spiders.html#scrapy.spiders.Spider.parse "scrapy.spiders.Spider.parse") 是Scrapy的默認回調方法,對沒有顯式分配回調的請求調用該方法。
### 提取數據
學習如何使用Scrapy提取數據的最佳方法是使用shell嘗試選擇器 [Scrapy shell](../topics/shell.html#topics-shell) . 運行:
```py
scrapy shell 'http://quotes.toscrape.com/page/1/'
```
注解
在從命令行運行Scrapy shell時,請記住始終將URL括在引號中,否則URL將包含參數(即。 `&` 字符)不起作用。
在Windows上,使用雙引號:
```py
scrapy shell "http://quotes.toscrape.com/page/1/"
```
您將看到類似的內容:
```py
[ ... Scrapy log here ... ]
2016-09-19 12:09:27 [scrapy.core.engine] DEBUG: Crawled (200) <GET http://quotes.toscrape.com/page/1/> (referer: None)
[s] Available Scrapy objects:
[s] scrapy scrapy module (contains scrapy.Request, scrapy.Selector, etc)
[s] crawler <scrapy.crawler.Crawler object at 0x7fa91d888c90>
[s] item {}
[s] request <GET http://quotes.toscrape.com/page/1/>
[s] response <200 http://quotes.toscrape.com/page/1/>
[s] settings <scrapy.settings.Settings object at 0x7fa91d888c10>
[s] spider <DefaultSpider 'default' at 0x7fa91c8af990>
[s] Useful shortcuts:
[s] shelp() Shell help (print this help)
[s] fetch(req_or_url) Fetch request (or URL) and update local objects
[s] view(response) View response in a browser
>>>
```
您可以使用shell試試選擇有 [CSS](https://www.w3.org/TR/selectors) 響應對象的元素:
```py
>>> response.css('title')
[<Selector xpath='descendant-or-self::title' data='<title>Quotes to Scrape</title>'>]
```
運行 `response.css('title')` 的輸出結果是一個列表形式的對象,即為 [`SelectorList`](../topics/selectors.html#scrapy.selector.SelectorList "scrapy.selector.SelectorList") 。這包含一組封裝了XML/HTML元素的 [`Selector`](../topics/selectors.html#scrapy.selector.Selector "scrapy.selector.Selector") 對象,并允許您進行進一步的查詢,以細化所選內容或提取數據。
要從以上標題中提取文本,可以執行以下操作:
```py
>>> response.css('title::text').getall()
['Quotes to Scrape']
```
這有兩件事需要注意:一是我們添加 `::text` 以查詢CSS,意味著我們只要直接選擇 `<title>` 元素內的文本元素。如果我們不指定 `::text` ,我們將得到包含標簽的完整title元素:
```py
>>> response.css('title').getall()
['<title>Quotes to Scrape</title>']
```
另外,調用 `.getall()` 的結果是個列表:一個選擇器有可能回返回多個結果,所以我們提取所有結果。假如您只想得到第一個結果的話,您可以這樣做:
```py
>>> response.css('title::text').get()
'Quotes to Scrape'
```
您也可以這樣寫:
```py
>>> response.css('title::text')[0].get()
'Quotes to Scrape'
```
不過,當[`SelectorList`](../topics/selectors.html#scrapy.selector.SelectorList "scrapy.selector.SelectorList") 實例找不到任何符合的元素時,直接使用 `.get()` 可避免 `IndexError` 和返回 `None` 。
課堂小知識:對于大多數的抓取代碼,當有找不到東西的時候,您會希望它能處理錯誤。這樣的話即使抓取失敗了,您至少可以得到**一些些**數據。
除了 [`getall()`](../topics/selectors.html#scrapy.selector.SelectorList.getall "scrapy.selector.SelectorList.getall") 和 [`get()`](../topics/selectors.html#scrapy.selector.SelectorList.get "scrapy.selector.SelectorList.get") 方法,您也可以以 [`re()`](../topics/selectors.html#scrapy.selector.SelectorList.re "scrapy.selector.SelectorList.re") 方法使用 [正則表達式](https://docs.python.org/zh-cn/3/library/re.html)提取數據:
```py
>>> response.css('title::text').re(r'Quotes.*')
['Quotes to Scrape']
>>> response.css('title::text').re(r'Q\w+')
['Quotes']
>>> response.css('title::text').re(r'(\w+) to (\w+)')
['Quotes', 'Scrape']
```
為了找到合適的CSS選擇器,您也許會發現在您的瀏覽器里的shell使用`view(response)`打開相應頁面會很有用 . 您可以使用瀏覽器內的帶有選擇器的開發者工具查看HTML(請參見關于 [使用瀏覽器的開發人員工具進行抓取](../topics/developer-tools.html#topics-developer-tools) )
[Selector Gadget](http://selectorgadget.com/) 工具可以快速找出CSS元素,其可支持多種瀏覽器。
#### xpath:簡介
除了 [CSS](https://www.w3.org/TR/selectors) ,scrapy選擇器也支持使用 [XPath](https://www.w3.org/TR/xpath) 表達式:
```py
>>> response.xpath('//title')
[<Selector xpath='//title' data='<title>Quotes to Scrape</title>'>]
>>> response.xpath('//title/text()').get()
'Quotes to Scrape'
```
XPath表達式是非常強大的,也是抓取選擇器的基礎。實際上,CSS選擇器其實是轉換為XPath。Shell中的選擇器對象所輸出的文字也可以發現這一點。
雖然也許不如CSS選擇器那么流行,但XPath表達式提供了更多的功能,因為除了導航結構之外,它還可以查看內容。使用XPath,您可以選擇如下內容:[*](#id1)選擇包含文本“下一頁”[*](#id3)的鏈接。這使得xpath非常適合于抓取任務,并且我們鼓勵您學習XPath,即使您已經知道如何構造css選擇器,它也會使抓取更加容易。
這里我們不介紹太多的XPath,但是您可以閱讀更多關于 [以Scrapy選擇器使用XPath](./10.md) . 要了解有關xpath的更多信息,我們建議 [this tutorial to learn XPath through examples](http://zvon.org/comp/r/tut-XPath_1.html) 和 [this tutorial to learn "how to think in XPath"](http://plasmasturm.org/log/xpath101/) .
#### 提取名言(quote)和作者
既然您對選擇和提取有了一些了解,那么讓我們通過編寫代碼從網頁中提取名言來完成 Spider 程序。
[http://quotes.toscrape.com](http://quotes.toscrape.com)中的每個引號都由如下所示的HTML元素表示:
```py
<div class="quote">
<span class="text">“The world as we have created it is a process of our
thinking. It cannot be changed without changing our thinking.”</span>
<span>
by <small class="author">Albert Einstein</small>
<a href="/author/Albert-Einstein">(about)</a>
</span>
<div class="tags">
Tags:
<a class="tag" href="/tag/change/page/1/">change</a>
<a class="tag" href="/tag/deep-thoughts/page/1/">deep-thoughts</a>
<a class="tag" href="/tag/thinking/page/1/">thinking</a>
<a class="tag" href="/tag/world/page/1/">world</a>
</div>
</div>
```
讓我們打開Scrapy Shell并了解如何提取所需數據:
```py
$ scrapy shell 'http://quotes.toscrape.com'
```
我們得到了quote的HTML元素的選擇器列表:
```py
>>> response.css("div.quote")
```
以上的查詢返回的每個選擇器都允許我們對其子元素運行進一步的查詢。讓我們將第一個選擇器賦給一個變量,這樣我們就可以直接在一個特定的quote上運行css選擇器:
```py
>>> quote = response.css("div.quote")[0]
```
現在,讓我們用剛剛創建的對象從使用 `quote` 的名言中提取 `text` , `author` 以及 `tags` :
```py
>>> title = quote.css("span.text::text").get()
>>> title
'“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'
```
鑒于標簽是字符串列表,我們可以使用 `.getall()` 方法獲取全部內容:
```py
>>> tags = quote.css("div.tags a.tag::text").getall()
>>> tags
['change', 'deep-thoughts', 'thinking', 'world']
```
了解了如何提取每一位之后,我們現在可以迭代所有quote元素,并將它們放在一個Python字典中:
```py
>>> for quote in response.css("div.quote"):
... text = quote.css("span.text::text").get()
... author = quote.css("small.author::text").get()
... tags = quote.css("div.tags a.tag::text").getall()
... print(dict(text=text, author=author, tags=tags))
{'tags': ['change', 'deep-thoughts', 'thinking', 'world'], 'author': 'Albert Einstein', 'text': '“The world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.”'}
{'tags': ['abilities', 'choices'], 'author': 'J.K. Rowling', 'text': '“It is our choices, Harry, that show what we truly are, far more than our abilities.”'}
... a few more of these, omitted for brevity
>>>
```
### 在 Spider 中提取數據
讓我們回到 Spider 。到目前為止,它還沒有提取任何數據,它只是將整個HTML頁面保存到一個本地文件中。讓我們把上面的提取邏輯集成到 Spider 中。
Scrapy Spider 通常會生成許多字典,其中包含從頁面中提取的數據。為此,我們使用 `yield` 回調python關鍵字,如下所示:
```py
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):
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(),
}
```
如果運行這個spider,它將會輸出提取的數據在日志中:
```py
2016-09-19 18:57:19 [scrapy.core.scraper] DEBUG: Scraped from <200 http://quotes.toscrape.com/page/1/>
{'tags': ['life', 'love'], 'author': 'André Gide', 'text': '“It is better to be hated for what you are than to be loved for what you are not.”'}
2016-09-19 18:57:19 [scrapy.core.scraper] DEBUG: Scraped from <200 http://quotes.toscrape.com/page/1/>
{'tags': ['edison', 'failure', 'inspirational', 'paraphrased'], 'author': 'Thomas A. Edison', 'text': "“I have not failed. I've just found 10,000 ways that won't work.”"}
```
## 存儲抓取的數據
存儲抓取數據的最簡單方法是使用 [Feed exports](../topics/feed-exports.html#topics-feed-exports) ,使用以下命令::
```py
scrapy crawl quotes -o quotes.json
```
會產生一個 `quotes.json` 包含所有已擦除項的文件,在 [JSON](https://en.wikipedia.org/wiki/JSON) .
出于歷史原因,scrapy會附加到給定的文件,而不是覆蓋其內容。如果在第二次運行此命令兩次之前不刪除該文件,則最終會得到一個損壞的JSON文件。
您還可以使用其他格式,例如 [JSON Lines](http://jsonlines.org) ::
```py
scrapy crawl quotes -o quotes.jl
```
這個 [JSON Lines](http://jsonlines.org) 格式很有用,因為它類似于流,您可以很容易地向它附加新記錄。當您運行兩次時,它不存在相同的JSON問題。另外,由于每個記錄都是單獨的一行,因此您可以處理大文件,而不必將所有內容都放入內存中,因此有如下工具: [JQ](https://stedolan.github.io/jq) 以幫助在命令行中執行此操作。
在小項目中(如本教程中的項目),這就足夠了。但是,如果您想對刮掉的項目執行更復雜的操作,可以編寫一個 [Item Pipeline](../topics/item-pipeline.html#topics-item-pipeline) . 項目創建時已為您設置了項目管道的占位符文件,位于 `tutorial/pipelines.py` . 但是,如果只想存儲刮掉的項目,則不需要實現任何項目管道。
## 以下鏈接
比如說,您不需要從http://quotes.toscrape.com的前兩頁抓取內容,而是需要從網站上所有頁面的引用。
既然您知道了如何從頁面中提取數據,那么讓我們看看如何從頁面中跟蹤鏈接。
第一件事是提取到我們要跟蹤的頁面的鏈接。檢查我們的頁面,我們可以看到有一個鏈接指向下一個帶有以下標記的頁面:
```py
<ul class="pager">
<li class="next">
<a href="/page/2/">Next <span aria-hidden="true">→</span></a>
</li>
</ul>
```
我們可以嘗試在Shell中提取:
```py
>>> response.css('li.next a').get()
'<a href="/page/2/">Next <span aria-hidden="true">→</span></a>'
```
這將獲取anchor元素,但我們需要該屬性 `href` . 為此,scrapy支持一個css擴展,讓您選擇屬性內容,如下所示:
```py
>>> response.css('li.next a::attr(href)').get()
'/page/2/'
```
還有一個 `attrib` 可用屬性(請參見 [選擇元素屬性](../topics/selectors.html#selecting-attributes) 更多):
```py
>>> response.css('li.next a').attrib['href']
'/page/2'
```
現在讓我們看看我們的spider被修改為遞歸地跟蹤下一頁的鏈接,從中提取數據:
```py
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)
```
現在,在提取數據之后, `parse()` 方法查找到下一頁的鏈接,并使用 [`urljoin()`](../topics/request-response.html#scrapy.http.Response.urljoin "scrapy.http.Response.urljoin") 方法(因為鏈接可以是相對的),并生成對下一頁的新請求,將自身注冊為回調,以處理下一頁的數據提取,并保持爬行在所有頁中進行。
這里您看到的是scrapy的以下鏈接機制:當您在回調方法中生成一個請求時,scrapy將計劃發送該請求,并注冊一個回調方法,以便在該請求完成時執行。
使用它,您可以構建復雜的爬蟲程序,這些爬蟲程序根據您定義的規則跟蹤鏈接,并根據所訪問的頁面提取不同類型的數據。
在我們的示例中,它創建了一種循環,跟蹤到下一頁的所有鏈接,直到找不到一個為止——這對于爬行博客、論壇和其他帶有分頁的站點很方便。
### 創建請求的快捷方式
作為創建請求對象的快捷方式,您可以使用 [`response.follow`](../topics/request-response.html#scrapy.http.TextResponse.follow "scrapy.http.TextResponse.follow") ::
```py
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('span small::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:
yield response.follow(next_page, callback=self.parse)
```
不像Scrapy.Request, `response.follow` 直接支持相對URL-無需調用URLJOIN。注意 `response.follow` 只返回一個請求實例;您仍然需要生成這個請求。
也可以將選擇器傳遞給 `response.follow` 而不是字符串;此選擇器應提取必要的屬性:
```py
for href in response.css('li.next a::attr(href)'):
yield response.follow(href, callback=self.parse)
```
為了 `<a>` 元素有一個快捷方式: `response.follow` 自動使用其href屬性。因此代碼可以進一步縮短:
```py
for a in response.css('li.next a'):
yield response.follow(a, callback=self.parse)
```
注解
`response.follow(response.css('li.next a'))` 無效,因為 `response.css` 返回一個類似列表的對象,其中包含所有結果的選擇器,而不是單個選擇器。一 `for` 像上面例子中那樣循環,或者 `response.follow(response.css('li.next a')[0])` 很好。
### 更多示例和模式
下面是另一個spider,它演示回調和以下鏈接,這次是為了抓取作者信息:
```py
import scrapy
class AuthorSpider(scrapy.Spider):
name = 'author'
start_urls = ['http://quotes.toscrape.com/']
def parse(self, response):
# follow links to author pages
for href in response.css('.author + a::attr(href)'):
yield response.follow(href, self.parse_author)
# follow pagination links
for href in response.css('li.next a::attr(href)'):
yield response.follow(href, 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'),
'bio': extract_with_css('.author-description::text'),
}
```
這個 Spider 將從主頁開始,它將跟蹤所有指向作者頁面的鏈接,調用 `parse_author` 它們的回調,以及與 `parse` 像我們以前看到的那樣回撥。
這里,我們把回電傳遞給 `response.follow` 作為使代碼更短的位置參數;它也適用于 `scrapy.Request` .
這個 `parse_author` 回調定義了一個助手函數,用于從CSS查詢中提取和清理數據,并用作者數據生成python dict。
這個 Spider 展示的另一個有趣的事情是,即使同一作者引用了很多話,我們也不需要擔心多次訪問同一作者頁面。默認情況下,scrappy過濾掉對已經訪問過的URL的重復請求,避免了由于編程錯誤而太多地訪問服務器的問題。這可以通過設置進行配置 [`DUPEFILTER_CLASS`](../topics/settings.html#std:setting-DUPEFILTER_CLASS) .
希望到目前為止,您已經很好地了解了如何使用scrappy跟蹤鏈接和回調的機制。
作為另一個利用以下鏈接機制的 Spider 示例,請查看 [`CrawlSpider`](../topics/spiders.html#scrapy.spiders.CrawlSpider "scrapy.spiders.CrawlSpider") 類,該類用于實現一個小規則引擎,您可以使用該引擎在上面編寫爬蟲程序。
另外,一個常見的模式是使用來自多個頁面的數據構建一個項目,使用 [trick to pass additional data to the callbacks](../topics/request-response.html#topics-request-response-ref-request-callback-arguments) .
## 使用 Spider 參數
通過使用 `-a` 運行它們時的選項:
```py
scrapy crawl quotes -o quotes-humor.json -a tag=humor
```
這些論點被傳給 Spider `__init__` 方法并默認成為spider屬性。
在本例中,為 `tag` 參數將通過 `self.tag` . 您可以使用它使您的 Spider 只獲取帶有特定標記的引號,并基于以下參數構建URL::
```py
import scrapy
class QuotesSpider(scrapy.Spider):
name = "quotes"
def start_requests(self):
url = 'http://quotes.toscrape.com/'
tag = getattr(self, 'tag', None)
if tag is not None:
url = url + 'tag/' + tag
yield scrapy.Request(url, self.parse)
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(),
}
next_page = response.css('li.next a::attr(href)').get()
if next_page is not None:
yield response.follow(next_page, self.parse)
```
如果您通過 `tag=humor` 對于這個 Spider ,您會注意到它只訪問來自 `humor` 標記,如 `http://quotes.toscrape.com/tag/humor` .
您可以 [learn more about handling spider arguments here](../topics/spiders.html#spiderargs) .
## 下一步
本教程只介紹 Scrapy 的基礎知識,但這里沒有提到很多其他特性。檢查 [還有什么?](overview.html#topics-whatelse) 段在 [Scrapy at a glance](overview.html#intro-overview) 第章快速概述最重要的部分。
您可以從該部分繼續 [基本概念](../index.html#section-basics) 為了了解更多關于命令行工具、spider、選擇器和其他一些本教程沒有涉及的內容,比如對抓取的數據建模。如果您喜歡使用示例項目,請檢查 [實例](examples.html#intro-examples) 部分。
- 簡介
- 第一步
- Scrapy at a glance
- 安裝指南
- Scrapy 教程
- 實例
- 基本概念
- 命令行工具
- Spider
- 選擇器
- 項目
- 項目加載器
- Scrapy shell
- 項目管道
- Feed 導出
- 請求和響應
- 鏈接提取器
- 設置
- 例外情況
- 內置服務
- Logging
- 統計數據集合
- 發送電子郵件
- 遠程登錄控制臺
- Web服務
- 解決具體問題
- 常見問題
- 調試spiders
- Spider 合約
- 常用做法
- 通用爬蟲
- 使用瀏覽器的開發人員工具進行抓取
- 調試內存泄漏
- 下載和處理文件和圖像
- 部署 Spider
- AutoThrottle 擴展
- Benchmarking
- 作業:暫停和恢復爬行
- 延伸 Scrapy
- 體系結構概述
- 下載器中間件
- Spider 中間件
- 擴展
- 核心API
- 信號
- 條目導出器
- 其余所有
- 發行說明
- 為 Scrapy 貢獻
- 版本控制和API穩定性