Scrapy 入門:爬蟲類詳解(Parse()函數、選擇器、提取數據)


安裝 & 創建項目

# 安裝Scrapy
pip install scrapy
# 創建項目
scrapy startproject tutorial # tutorial為項目名
# 創建爬蟲
scrapy genspider <爬蟲名> <domain.com>

得到的目錄結構如下:

tutorial/
    scrapy.cfg            # 配置文件
    tutorial/             # 項目的模塊
        __init__.py
        items.py          # 定義items
        middlewares.py    # 中間件
        pipelines.py      # pipelines
        settings.py       # 設置文件
        spiders/          # 爬蟲
            __init__.py
            spider1.py
            ...

爬蟲類

爬蟲類必須繼承 scrapy.Spider,爬蟲類中必要的屬性和方法:

1. name = "quotes":爬蟲名,必須唯一,因為需要使用 scrapy crawl "爬蟲名" 命令用來開啟指定的爬蟲。

2. start_requests():要求返回一個 requests 的列表或生成器,爬蟲將從 start_requests() 提供的 requests 中爬取,例如:

# start_requests()
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)

3. parse():用於處理每個 Request 返回的 Response 。parse() 通常用來將 Response 中爬取的數據提取為數據字典,或者過濾出 URL 然后繼續發出 Request 進行進一步的爬取。

# 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)

4. start_urls 列表:可以在爬蟲類中定義一個名為 start_urls 的列表替代 start_requests() 方法。作用同樣是為爬蟲提供初始的 Requests,但代碼更加的簡潔。

運行爬蟲后,名為 parse() 的方法將會被自動調用,用來處理 start_url 列表中的每一個 URL:

start_urls = [
    'http://quotes.toscrape.com/page/1/',
    'http://quotes.toscrape.com/page/2/',
]

5. 運行爬蟲

$ scrapy crawl quotes

運行爬蟲時發生了什么:Scrapy 通過爬蟲類的 start_requests 方法返回 scrapy.Request 對象。在接收到每個 response 響應時,它實例化 Response 對象並調用與 request 相關的回調方法( parse 方法 ),並將 Response 作為其參數傳遞。

parse() 函數

parse() 函數無疑是爬蟲類中最重要的函數,它包含了爬蟲解析響應的主要邏輯。

學習使用 Scrapy 選擇器的最佳方法就是使用 Scrapy shell,輸入這個命令之后將會進入一個交互式的命令行模式:

scrapy shell 'http://quotes.toscrape.com/page/1/'

下面將通過交互式命令實踐來學習 Response 選擇器:

CSS 選擇器

response.css 返回的是一個 SelectorList 對象,它是一個Selector 對象構成的列表,例如:

>>> response.css('title')
[<Selector xpath='descendant-or-self::title' data='<title>Quotes to Scrape</title>'>]

getall() 方法獲取所有符合條件的字符串列表,用 get() 獲取首個匹配的字符串。::text 用於去除標簽(<tag>)。

>>> response.css('title::text').getall()
['Quotes to Scrape']
>>> response.css('title::text').get()
'Quotes to Scrape'
>>> response.css('title::text')[0].get()
'Quotes to Scrape'

使用 re() 相當於在 getall() 的基礎上用正則表達式對內容進一步篩選

>>> response.css('title::text').re(r'Q\w+')
['Quotes']

XPath 選擇器

XPath 選擇器相較於 CSS 選擇器更加強大。實際上在 Scrapy 內部,CSS 選擇器最終會被轉換成 XPath 選擇器。

>>> response.xpath('//title')
[<Selector xpath='//title' data='<title>Quotes to Scrape</title>'>]
>>> response.xpath('//title/text()').get()
'Quotes to Scrape'

生成數據字典

要將 Response 中爬取的數據生成為數據字典,使用字典生成器,例如:

def parse(self, response):
    for quote in response.css('div.quote'):  # quote是SelectorList對象
        yield {
            'text': quote.css('span.text::text').get(),
            'author': quote.css('small.author::text').get(),
            'tags': quote.css('div.tags a.tag::text').getall(),
        }

存儲數據到文件

最簡單的方法是用 Feed exports。使用 -o 參數指定一個 json 文件用於存儲 parse() 函數 yield 出的內容。

$ scrapy crawl quotes -o quotes.json -s FEED_EXPORT_ENCODING=utf-8
# 若有中文務必加上 -s FEED_EXPORT_ENCODING=utf-8

使用 JSON Lines 格式存儲。由於歷史原因,Scrapy 只會追加而非覆蓋原先的 Json 文件,會導致第二次寫入后 Json 格式被破壞。而使用 JSON Lines 格式 ( .jl )可以避免這個問題

$ scrapy crawl quotes -o quotes.jl

要對數據進行更多的操作(例如驗證爬到的數據,去重等等),可以在 pipelines.py 中寫一個 Item Pipeline。當然,如果只需要存儲爬取到的數據則不需要。

提取 URL 進行深層爬取

例如要提取出下一頁的 URL 地址進行進一步的爬取:

<li class="next">
    <a href="/page/2/">Next <span aria-hidden="true">&rarr;</span></a> <!-- &rarr;表示右箭頭 -->
</li>

通過以下兩種方式都可以提取出 <a> 標簽中的 href 屬性:

>>> response.css('li.next a::attr(href)').get()
'/page/2/'
>>> response.css('li.next a').attrib['href']
'/page/2'

當在 parse() 中 yield 出的是一個 Request 對象時,Scrapy 會自動安排發送這個 request,當請求完成后繼續調用 callback 參數所指定的回調函數,如下所示:

def parse(self, response):
    for quote in response.css('div.quote'):  # quote是SelectorList對象
        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)  # urljoin()方法可以自動將相對路徑轉換為絕對路徑
        yield scrapy.Request(next_page, callback=self.parse)  # yield scrapy.Request()

response.follow()

建議使用更方便的 response.follow() 替代 scrapy.Request(),因為它直接支持相對路徑,上文中代碼可以簡化如下:

next_page = response.css('li.next a::attr(href)').get()
if next_page is not None:
    yield response.follow(next_page, callback=self.parse)  # next_page = '/page/2/'

response.follow() 還支持直接使用 Selector 對象作為參數,無需提取出 URL,於是上述代碼得到進一步簡化:

for href in response.css('li.next a::attr(href)'):
    yield response.follow(href, callback=self.parse)  # href = [<Selector xpath='' data=''>]

注意 SelectorList 對象不能直接作為參數,下面的用法是錯誤的:
yield response.follow(response.css('li.next a::sattr(href)'), callback=self.parse)

針對 <a> 標簽的 css 選擇器,response.follow() 會自動使用其 href 屬性,於是上述代碼終極簡化版本如下所示:

# CSS選擇器
for a in response.css('li.next a'):
    yield response.follow(a, callback=self.parse)

但是注意 XPath 選擇器不能這么簡寫:

# 不能簡化成 //div[@class='p_name']/a
for a in response.xpath("//div[@class='p_name']/a/@href"):
    yield response.follow(a, callback=self.parse) 

默認情況下,Scrapy 會幫我們過濾掉重復訪問的地址,可以通過 DUPEFILTER_CLASS Setting 設置。

scrapy crawl 附帶參數

使用 -a 選項來給爬蟲提供額外的參數,提供的參數會自動變成爬蟲類的屬性(使用 self.tag 或 getattr(self, 'tag', None) 獲取),如下例,使用 -a tag=humor 命令行參數,最終數據將保存到 quotes-humor.json 文件:

$ scrapy crawl quotes -o quotes-humor.json -a tag=humor
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)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM