8-Scrapy框架匯總

scrapy架構(gòu)

scrapy.png

Scrapy主要組件
1、引擎(Scrapy): 用來處理整個系統(tǒng)的數(shù)據(jù)流處理, 觸發(fā)事務(wù)(框架核心)接箫。
2虫给、調(diào)度器(Scheduler): 用來接受引擎發(fā)過來的請求, 壓入隊列中, 并在引擎再次請求的時候返回. 可以想像成一個URL(抓取網(wǎng)頁的網(wǎng)址或者說是鏈接)的優(yōu)先隊列, 由它來決定下一個要抓取的網(wǎng)址是什么, 同時去除重復(fù)的網(wǎng)址。
3咏窿、下載器(Downloader): 用于下載網(wǎng)頁內(nèi)容, 并將網(wǎng)頁內(nèi)容返回給蜘蛛(Scrapy下載器是建立在twisted這個高效的異步模型上的)砖第。
4撤卢、爬蟲(Spiders): 爬蟲是主要干活的, 用于從特定的網(wǎng)頁中提取自己需要的信息, 即所謂的實體(Item)。用戶也可以從中提取出鏈接,讓Scrapy繼續(xù)抓取下一個頁面梧兼。
5放吩、項目管道(Pipeline): 負(fù)責(zé)處理爬蟲從網(wǎng)頁中抽取的實體,主要的功能是持久化實體袱院、驗證實體的有效性屎慢、清除不需要的信息。當(dāng)頁面被爬蟲解析后忽洛,將被發(fā)送到項目管道腻惠,并經(jīng)過幾個特定的次序處理數(shù)據(jù)。
6欲虚、下載器中間件(Downloader Middlewares): 位于Scrapy引擎和下載器之間的框架集灌,主要是處理Scrapy引擎與下載器之間的請求及響應(yīng)。
7、爬蟲中間件(Spider Middlewares): 介于Scrapy引擎和爬蟲之間的框架欣喧,主要工作是處理蜘蛛的響應(yīng)輸入和請求輸出腌零。
8、調(diào)度中間件(Scheduler Middewares): 介于Scrapy引擎和調(diào)度之間的中間件唆阿,從Scrapy引擎發(fā)送到調(diào)度的請求和響應(yīng)益涧。

安裝

  1. 首先考慮使用最簡單的方法安裝,可能會有諸多錯誤驯鳖,scrapy安裝需要Lxml闲询、Twisted等庫。
pip install scrapy  
  • Failed building wheel for lxml
  • Microsoft Visual C++ 10.0 is required
  • Failed building twisted
  • Unable to find vcvarsall.bat
  1. 直接使用pip install scrapy安裝不成功可以安裝whl格式的包

    首先下載scrapy的whl包浅辙,下載地址:http://www.lfd.uci.edu/~gohlke/pythonlibs/

    搜索scrapy 找到Scrapy-1.5.0-py2.py3-none-any.whl扭弧,下載后不要安裝。

    scrapy依賴twiste记舆,同樣使用whl格式的包進行安裝

    還是進入http://www.lfd.uci.edu/~gohlke/pythonlibs/鸽捻,在網(wǎng)頁中搜索twisted找到其對應(yīng)的whl包并下載.

    下載完成后使用cmd打開windows的命令行窗口,進入whl包所在的文件夾執(zhí)行如下命令

    pip install [whl],[whl]是whl包的名字.

    scrapy依賴lxml包泽腮,需要先安裝lxml包. pip install lxml即可御蒲,再安裝twisted,最后安裝scrapy.

    安裝完成后使用scrapy -h測試是否安裝成功

  2. windows系統(tǒng)安裝完成后運行scrapy可能會報no model named win32錯誤,到https://sourceforge.net/projects/pywin32/files/下載直接安裝即可盛正。

scrapy命令

C:\Users\Administrator>scrapy
Scrapy 1.5.0 - no active project

Usage:
  scrapy <command> [options] [args]

Available commands:
  bench         Run quick benchmark test
  fetch         Fetch a URL using the Scrapy downloader
  genspider     Generate new spider using pre-defined templates
  runspider     Run a self-contained spider (without creating a project)
  settings      Get settings values
  shell         Interactive scraping console
  startproject  Create new project
  version       Print Scrapy version
  view          Open URL in browser, as seen by Scrapy
  list          List available spiders
  parse         Parse URL (using its spider) and print the results
  check         Check spider contracts
  crawl         Run a spider
  edit          Edit spider
  

  [ more ]      More commands available when run from project directory

Use "scrapy <command> -h" to see more info about a command
# bench 做測試用删咱,反映當(dāng)前性能,爬蟲速度
scrapy bench

# fetch 幫助我們下載網(wǎng)頁豪筝,將網(wǎng)頁源代碼返回(前面是一些日志,后面是源代碼)
scrapy fetch url

#生成爬蟲
scrapy genspider +文件名+網(wǎng)址

# runspider運行爬蟲文件摘能,與crawl的去區(qū)別是runspider運行的是spider.py文件续崖,而crawl運行整個項目
scrapy runspider spider.py

# Get settings values
scrapy settings --get BOT_NAME
scrapybot
scrapy settings --get DOWNLOAD_DELAY
0

# shell命令, 進入scrpay交互環(huán)境,主要使用這里面的response命令, 例如response.xpath() 括號里直接加xpath路徑
scrapy shell url

#創(chuàng)建項目
scrapy startproject demo

#查看scrapy版本  -v可以輸出依賴庫的版本
scrapy version -v

# view請求Url,把它的網(wǎng)頁源代碼保存成文件,并打開網(wǎng)頁
scrapy view http://www.example.com/some/page.html

#查看爬蟲列表
scrapy list

#check檢查錯誤
scrapy check

# 運行(crawl)
scrapy crawl +爬蟲名稱

#使用 EDITOR 中設(shè)定的編輯器編輯給定的spider
#該命令僅僅是提供一個快捷方式团搞。開發(fā)者可以自由選擇其他工具或者IDE來編寫調(diào)試spider严望。
scrapy edit spider1

settings.py配置

# USER_AGENT 設(shè)置用戶代理
USER_AGENT = 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36 QIHU 360SE'

#設(shè)置是否遵守robots協(xié)議
ROBOTSTXT_OBEY = True

# 設(shè)置抓取中文顯示編碼
FEED_EXPORT_ENCODING = 'utf-8'

# pipelines激活
ITEM_PIPELINES = {
    'BtMovie.pipelines.BtmoviePipeline': 300,
}

scrapy抓取網(wǎng)站

一般需要四個步驟

  1. 創(chuàng)建一個爬蟲項目
  2. 定義Item容器
  3. 編寫爬蟲,提取數(shù)據(jù)
  4. 存儲內(nèi)容

創(chuàng)建項目

在開始爬取之前逻恐,您必須創(chuàng)建一個新的Scrapy項目像吻。 進入您打算存儲代碼的目錄中,運行下列命令:

scrapy startproject tutorial

該命令將會創(chuàng)建包含下列內(nèi)容的 tutorial 目錄:

tutorial/
    scrapy.cfg
    tutorial/
        __init__.py
        items.py
        pipelines.py
        settings.py
        spiders/
            __init__.py
            ...

這些文件分別是:

  • scrapy.cfg: 項目的配置文件
  • tutorial/: 該項目的python模塊复隆。之后您將在此加入代碼拨匆。
  • tutorial/items.py: 項目中的item文件.
  • tutorial/pipelines.py: 項目中的pipelines文件.
  • tutorial/settings.py: 項目的設(shè)置文件.
  • tutorial/spiders/: 放置spider代碼的目錄.

定義Item

Item 是保存爬取到的數(shù)據(jù)的容器;其使用方法和python字典類似挽拂, 并且提供了額外保護機制來避免拼寫錯誤導(dǎo)致的未定義字段錯誤惭每。

類似在ORM中做的一樣,您可以通過創(chuàng)建一個 scrapy.Item 類亏栈, 并且定義類型為 scrapy.Field 的類屬性來定義一個Item台腥。

首先根據(jù)需要從toscrape.com獲取到的數(shù)據(jù)對item進行建模宏赘。 我們需要從quotes.py中獲取名字,url黎侈,以及網(wǎng)站的描述察署。 對此,在item中定義相應(yīng)的字段峻汉。編輯 tutorial 目錄中的 items.py 文件:

class TutorialItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    text = scrapy.Field()
    author = scrapy.Field()
    tags = scrapy.Field()

編寫第一個爬蟲(Spider)

Spider是用戶編寫用于從單個網(wǎng)站(或者一些網(wǎng)站)爬取數(shù)據(jù)的類贴汪。

其包含了一個用于下載的初始URL,如何跟進網(wǎng)頁中的鏈接以及如何分析頁面中的內(nèi)容俱济, 提取生成 item 的方法嘶是。

為了創(chuàng)建一個Spider,您必須繼承 scrapy.Spider 類蛛碌, 且定義以下三個屬性:

  • name: 用于區(qū)別Spider聂喇。 該名字必須是唯一的,您不可以為不同的Spider設(shè)定相同的名字蔚携。
  • start_urls: 包含了Spider在啟動時進行爬取的url列表希太。 因此,第一個被獲取到的頁面將是其中之一酝蜒。 后續(xù)的URL則從初始的URL獲取到的數(shù)據(jù)中提取誊辉。
  • parse() 是spider的一個方法。 被調(diào)用時亡脑,每個初始URL完成下載后生成的 Response 對象將會作為唯一的參數(shù)傳遞給該函數(shù)堕澄。 該方法負(fù)責(zé)解析返回的數(shù)據(jù)(response data),提取數(shù)據(jù)(生成item)以及生成需要進一步處理的URL的 Request 對象霉咨。

以下為我們的第一個Spider代碼蛙紫,保存在 tutorial/spiders 目錄下的 quotes.py 文件中:

import scrapy

class QuotesSpider(scrapy.Spider):
    name = "quotes"
    # allowed_domains = ['toscrape.com']
    
    # 簡潔寫法
    '''
    start_urls = [
            'http://quotes.toscrape.com/page/1/',
            'http://quotes.toscrape.com/page/2/',
    ]
    '''
    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)
    
    #回調(diào)函數(shù)
    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)

爬取

進入項目的根目錄,執(zhí)行下列命令啟動spider:

#爬蟲的名字就是quotes.py中的name
scrapy crawl quotes

scrapy crawl quotes --nolog不生成日志文件

或者在spiders目錄下創(chuàng)建run.py寫入

from scrapy import cmdline

# -o表示文件名 -t表示文件格式
cmdline.execute("scrapy crawl news -o news.json -t json".split())

提取Item

Selectors選擇器簡介

從網(wǎng)頁中提取數(shù)據(jù)有很多方法途戒。Scrapy使用了一種基于 XPathCSS 表達式機制: Scrapy Selectors坑傅。 關(guān)于selector和其他提取機制的信息請參考 Selector文檔

這里給出XPath表達式的例子及對應(yīng)的含義:

  • /html/head/title: 選擇HTML文檔中 head 標(biāo)簽內(nèi)的 title 元素
  • /html/head/title/text(): 選擇上面提到的 title 元素的文字
  • //td: 選擇所有的 <td> 元素
  • //div[@class="mine"]: 選擇所有具有 class="mine" 屬性的 div 元素

上邊僅僅是幾個簡單的XPath例子喷斋,XPath實際上要比這遠遠強大的多唁毒。 如果您想了解的更多,我們推薦 這篇XPath教程 星爪。

為了配合XPath浆西,Scrapy除了提供了 Selector 之外,還提供了方法來避免每次從response中提取數(shù)據(jù)時生成selector的麻煩移必。

Selector有四個基本的方法(點擊相應(yīng)的方法可以看到詳細(xì)的API文檔):

  • xpath(): 傳入xpath表達式室谚,返回該表達式所對應(yīng)的所有節(jié)點的selector list列表 。
  • css(): 傳入CSS表達式,返回該表達式所對應(yīng)的所有節(jié)點的selector list列表.
  • extract(): 序列化該節(jié)點為unicode字符串并返回list秒赤。
  • re(): 根據(jù)傳入的正則表達式對數(shù)據(jù)進行提取猪瞬,返回unicode字符串list列表。

在Shell中嘗試Selector選擇器

為了介紹Selector的使用方法入篮,接下來我們將要使用內(nèi)置的 Scrapy shell 陈瘦。Scrapy Shell需要您預(yù)裝好IPython(一個擴展的Python終端)。

您需要進入項目的根目錄潮售,執(zhí)行下列命令來啟動shell:

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

You will see something like:

[ ... 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

當(dāng)shell載入后痊项,您將得到一個包含response數(shù)據(jù)的本地 response 變量。輸入 response.body 將輸出response的包體酥诽, 輸出 response.headers 可以看到response的包頭鞍泉。

更為重要的是,當(dāng)輸入 response.selector 時肮帐, 您將獲取到一個可以用于查詢返回數(shù)據(jù)的selector(選擇器)咖驮, 以及映射到 response.selector.xpath()response.selector.css() 的 快捷方法(shortcut): response.xpath()response.css() 训枢。

Css selector

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

>>> response.css('title').extract()
['<title>Quotes to Scrape</title>']

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

>>> response.css('title::text').extract()
['Quotes to Scrape']

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

>>> response.css('title::text').extract_first()
'Quotes to Scrape'

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

# 正則表達式
>>> 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']

Each quote in http://quotes.toscrape.com is represented by HTML elements that look like this:

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

進入shell

$ scrapy shell 'http://quotes.toscrape.com'

>>> response.css("div.quote")
>>> quote = response.css("div.quote")[0]

>>> title = quote.css("span.text::text").extract_first()
>>> 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").extract_first()
>>> author
'Albert Einstein'


>>> tags = quote.css("div.tags a.tag::text").extract()
>>> tags
['change', 'deep-thoughts', 'thinking', 'world']
<ul class="pager">
    <li class="next">
        <a href="/page/2/">Next <span aria-hidden="true">&rarr;</span></a>
    </li>
</ul>


>>> response.css('li.next a').extract_first()
'<a href="/page/2/">Next <span aria-hidden="true">→</span></a>'

>>> response.css('li.next a::attr(href)').extract_first()
'/page/2/'

Xpath selector

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

我們可以通過這段代碼選擇該頁面中網(wǎng)站列表里所有 <li> 元素:

response.xpath('//ul/li')

網(wǎng)站的描述:

response.xpath('//ul/li/text()').extract()

網(wǎng)站的標(biāo)題:

response.xpath('//ul/li/a/text()').extract()

以及網(wǎng)站的鏈接:

response.xpath('//ul/li/a/@href').extract()

string()和text()的區(qū)別托修,以原生xpath例子

from lxml import etree

html = """
    <p>我在這里我我我我<b>你呢</b>在哪里</p><p>oooo</p>
"""
tree = etree.HTML(html)
print(type(tree)) #  <class 'lxml.etree._Element'>
content = tree.xpath('//p/text()')
# ['我在這里我我我我', '在哪里', 'oooo']
# p標(biāo)簽下的 b標(biāo)簽不再被解析 第一個p中的文字分成了2部分
# 而這種情況是無法使用string()的,因為所有p是一個list, string方法只能對單個element使用恒界,所有需要遍歷


content2 = tree.xpath('//p')   # [<Element p at 0x22fff46af08>, <Element p at 0x22fff480048>]
for p in content2:
    print(p.xpath('string(.)'))
# 我在這里我我我我你呢在哪里
# oooo
for p in content2:
    print(p.xpath('text()'))
# ['我在這里我我我我', '在哪里']
# ['oooo']

總結(jié):text()獲得的總是一個list睦刃,而string()直接獲得一個字符串

提取數(shù)據(jù)

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"):
         text = quote.css("span.text::text").extract_first()
         author = quote.css("small.author::text").extract_first()
         tags = quote.css("div.tags a.tag::text").extract()
         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

    

使用item

import scrapy
from tutorial.items import TutorialItem

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').extract_first(),
                'author': quote.css('small.author::text').extract_first(),
                'tags': quote.css('div.tags a.tag::text').extract(),
            }
         '''
         item = TutorialItem()
         # item = {}
         item['text'] = quote.css("span.text::text").extract_first()
         item['author']  = quote.css("small.author::text").extract_first()
         item['tags']  = quote.css("div.tags a.tag::text").extract()
         yield item

保存爬取到的數(shù)據(jù)

scrapy crawl quotes -o items.json

? 在這樣小規(guī)模的項目中,這種存儲方式已經(jīng)足夠十酣。 如果需要對爬取到的item做更多更為復(fù)雜的操作涩拙,可以編寫 piplines.py文件。

爬取下一頁

我們既然要爬取下一頁耸采,那我們首先要分析鏈接格式吃环,找到下一頁的鏈接。

<li class="next">
    <a >下一頁 ?</a>
</li>

那到底如何讓蜘蛛自動的判斷洋幻、并爬取下一頁、下一頁的內(nèi)容呢翅娶?我們可以這樣來做文留,我們每爬一頁就用css選擇器來查詢,是否存在下一頁鏈接竭沫,存在:則爬取下一頁鏈接http://lab.scrapyd.cn/page/*/ 燥翅,然后把下一頁鏈接提交給當(dāng)前爬取的函數(shù),繼續(xù)爬取蜕提,繼續(xù)查找下一頁森书,知道找不到下一頁,說明所有頁面已經(jīng)爬完,那結(jié)束爬蟲凛膏。

import scrapy
from tutorial.items import TutorialItem

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').extract_first(),
                'author': quote.css('small.author::text').extract_first(),
                'tags': quote.css('div.tags a.tag::text').extract(),
            }
         
    next_page = response.css('li.next a::attr(href)').extract_first()  
    if next_page is not None: 
        next_page = response.urljoin(next_page)
        yield scrapy.Request(next_page, callback=self.parse)
def start_requests():
        yield  scrapy.Request(url,callback=self.page1)
def page1():
        yield  scrapy.Request(url,callback=self.page2)
def page2():
        yield item

官方教程例子:

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):
            #str.strip()
            return response.css(query).extract_first().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'),
        }

Scrapy followlinks

scrapy.Request需要一個絕對的url地址杨名,所以使用了urljoin()方法生成絕對地址。方法1

response.follow不用獲取到絕對的url猖毫,使用follow方法會自動幫我們實現(xiàn)台谍。方法2

follow還可以不用獲取url字符串,只需要傳入一個selector 吁断。This selector should extract necessary attributes.方法3

<a>標(biāo)簽有一個簡寫趁蕊,response.follow可以自動使用它們的屬性。方法4

注意傳入的對象只能是str或selector仔役,不能是SelectorList

  • 方法1
next_page = response.css('li.next a::attr(href)').extract_first()  
    if next_page is not None: 
        next_page = response.urljoin(next_page)
        yield scrapy.Request(next_page, callback=self.parse)
  • 方法2
next_page = response.css('li.next a::attr(href)').extract_first()  
    if next_page is not None: 
        yield response.follow(next_page, callback=self.parse)
  • 方法3
for href in response.css('li.next a::attr(href)')
    yield response.follow(href, callback=self.parse)
  • 方法4
for href in response.css('li.next a')
    yield response.follow(href, callback=self.parse)

爬取BT電影

# -*- coding: utf-8 -*-
import scrapy
from BtMovie.items import BtmovieItem

class BtdySpider(scrapy.Spider):
    name = 'btdy'
    # allowed_domains = ['btbtdy.net']
    start_urls = ['http://www.btbtdy.net/']

    def parse(self, response)
        links = response.xpath('//div[@class="cts_ms"]/p/a/@href').extract()
        for link in links:
            print(link)
            yield response.follow(link,callback=self.parse_content)

        next_page = response.xpath('//div[@class="pages"]/a/@href').extract_first()
        if next_page is not None:
            print(next_page)
            next_page = response.urljoin(next_page)
            yield scrapy.Request(next_page,callback=self.parse)

    def parse_content(self,response):
        # print(response.xpath('//title'))
        movie = BtmovieItem()
        title = response.xpath('//h1/text()').extract_first()
        content = response.xpath('//div[@class="c05"]/span/text()').extract_first()
        magnet = response.xpath('//*[@id="nucms_downlist"]/div[2]/ul/li/span/a/@href').extract_first()
        movie['title'] = title
        movie['content'] = content
        movie['magnet'] = magnet
        yield movie

傳遞參數(shù)

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').extract_first(),
                'author': quote.css('small.author::text').extract_first(),
            }

        next_page = response.css('li.next a::attr(href)').extract_first()
        if next_page is not None:
            yield response.follow(next_page, self.parse)

使用-a傳遞參數(shù),這些參數(shù)傳遞到爬蟲類的__init__

scrapy crawl quotes  -o quotes-humor.json  -a tag=humor

#url = 'http://quotes.toscrape.com/tag/humor'

傳遞參數(shù)demo

編寫爬蟲文件 job.py

#框架已經(jīng)自動創(chuàng)建以下內(nèi)容
import scrapy

class JobSpider(scrapy.Spider):
    name = 'job'  
    allowed_domains = ['51job.com'] 
    start_urls = ['http://51job.com/'] 
    def parse(self, response): 
        pass
#name 是爬蟲的名稱    
#allowed_domains是指允許爬行的域名
#start_urls 是爬行的起始網(wǎng)址掷伙,可以定義多個,用逗號隔開
#如果沒有特別的回調(diào)函數(shù)又兵,該方法是處理acrapy爬蟲爬行到的網(wǎng)頁響應(yīng)(response)額默認(rèn)方法任柜,可以對響應(yīng)進行處理冰返回處理后的數(shù)據(jù),也負(fù)責(zé)鏈接的跟蹤寒波。



#對初始內(nèi)容進行修改
import scrapy
from jobproject.items import JobprojectItem

class JobSpider(scrapy.Spider):
    name = 'job'  
    allowed_domains = ['51job.com'] 
    start_urls = ['http://51job.com/'] 
    
    #重新初始化方法乘盼,并設(shè)置參數(shù)place,這樣運行時候就可以加上-a place參數(shù)值賦值了
    def __init__(self,place,*args,**kwargs):
        super().__init__(*args,**kwargs)
        #設(shè)置默認(rèn)地址為杭州
        if place is None:
            self.place = '杭州'
        else:
            self.place=place
      #重寫start_requests方法俄烁,將起始網(wǎng)址設(shè)置未從urls中讀取绸栅。      
     def start_requests(self):
        urls = ['https://search.51job.com/list/{place_code},000000,0000,00,9,99,python,2,1.html'.format(place_code=self.get_place_code())]
         for url in urls:
           #make_requests_from_url默認(rèn)方法實現(xiàn)生成Request的請求對象
            yield self.make_requests_from_url(url)

    def get_place_code(self):
        # 51job搜索時候的地址碼,例如搜索杭州python地址如下
        #https://search.51job.com/list/080200,000000,0000,00,9,99,python,2,1.html
        place_map={
            '杭州':'080200',
            '上海':'020000'
        }
        return place_map.get(self.place)

    def parse(self, response):
        print(response.xpath('//title/text()'))
        jobs = response.xpath('//*[@id="resultList"]/div[@class="el"]')
        for job in jobs:
            item = JopItem()
            item['name']=job.xpath('.//p/span/a/text()').extract_first().strip()
            item['money'] = job.xpath('.//span[@class="t4"]/text()').extract_first().strip()
            yield item

數(shù)據(jù)的保存

將數(shù)據(jù)保存為json格式

pipelines.py

import json
import pymysql
import pymongo
from scrapy.contrib.exporter import JsonItemExporter

class JsonPipeline(object):

    def __init__(self):
        self.json_file = open('job.json','w')

    def process_item(self, item, spider):
        json_item = json.dumps(dict(item))
        print(json_item)
        print(json_item.strip())
        self.json_file.write(json_item)
        self.json_file.write('\n')
        return item


    def close_spider(self,spider):
        print("close_spider is call.")
        self.json_file.close()


# 使用系統(tǒng)自帶
class ScrapyJsonPipeline(object):
    def __init__(self):
        self.json_file = open('job_scrapy.json','wb')
        self.exporter = JsonItemExporter(self.json_file,encoding='utf-8')
        self.exporter.start_exporting()


    def process_item(self,item,spider):
        print(item)
        self.exporter.export_item(item)
        return item

    def close_spider(self,spider):
        self.exporter.finish_exporting()

將數(shù)據(jù)存儲到mysql和mongodb

pipelines.py

class MySQLPipeline(object):
    """
    pymyql
    """
    def __init__(self):
        """
        連接數(shù)據(jù)庫
        """
        self.conn = pymysql.connect(host='127.0.0.1', port=3306,user='root',
                                    password='123456', db='spider', charset='utf8')
        self.cursor = self.conn.cursor()

        
    def process_item(self,item,spider):
        """
        把每條數(shù)據(jù)插入數(shù)據(jù)庫
        """
        sql = "insert into qcwy(name) value(%s)"
        self.cursor.execute(sql, (item['name'],))
        self.conn.commit()
        return item


    def close_spider(self,spider):
        """
        關(guān)閉數(shù)據(jù)庫連接
        """
        self.cursor.close()
        self.conn.close()

class MongoDBPipeline(object):
    """
    pymongo
    """
    def __init__(self):
        """
        連接mongo
        """
        #創(chuàng)建一個客戶端
        self.client = pymongo.MongoClient(host='127.0.0.1',port=27017)
        self.db = self.client['job']

        self.coll = self.db['job_collection']

    def process_item(self,item,spider):
        dict_item = dict(item)
        self.coll.insert(dict_item)
        return item


    def close_spider(self,spider):
        self.client.close()

最后都需要在settings.py中配置

ITEM_PIPELINES = {
    #'JobSpider.pipelines.JobspiderPipeline': 300,
    'JobSpider.pipelines.JsonPipeline': 301,
    'JobSpider.pipelines.ScrapyJsonPipeline': 302,
    'JobSpider.pipelines.MySQLPipeline':303,
    'JobSpider.pipelines.MongoDBPipeline':304,
}

Selenium

Selenium 是一個用于Web應(yīng)用程序測試的工具页屠。Selenium測試直接運行在瀏覽器中粹胯,就像真正的用戶在操作一樣。支持的瀏覽器包括IE(7, 8, 9, 10, 11)辰企,Mozilla Firefox风纠,Safari,Google Chrome牢贸,Opera等竹观。這個工具的主要功能包括:測試與瀏覽器的兼容性——測試你的應(yīng)用程序看是否能夠很好得工作在不同瀏覽器和操作系統(tǒng)之上。測試系統(tǒng)功能——創(chuàng)建回歸測試檢驗軟件功能和用戶需求潜索。支持自動錄制動作和自動生成 .Net臭增、Java、Perl等不同語言的測試腳本竹习。

Selenium安裝

pip install selenium

安裝driver

Firefox瀏覽器需安裝geckdriver誊抛,Chrome瀏覽器需要安裝chromedriver,IE瀏覽器要安裝IEdriver整陌。

可以在npm淘寶鏡像中下載拗窃。下載chrome驅(qū)動 解壓放到python解釋器所在目錄

什么是npm瞎领?

npm(node package manager)node的包管理工具.簡單地說npm就是一個基于nodejs的包管理器,它管理的是javascript随夸。

? 舉例來說:如果我們在開發(fā)過程中使用jquery九默,那么是不是要引入jquery,你可能會下載這個jquery.js文件逃魄,然后在代碼中<script src="jquery.js"></script>是吧 荤西。如果使用 npm ,那么就方便了伍俘,直接在npm下使用命令:$ npm install jquery邪锌;就自動下載了。

    在遠端有一個npm服務(wù)器癌瘾,里面有很多別人寫的代碼觅丰,我們可以直接使用npm下載使用。

    同時你也可以把自己寫的代碼推送到npm 服務(wù)器妨退,讓別人使用妇萄。

包管理工具還有Ubuntu的apt-get晾虑,CentOS的yum瑰剃,微軟的Nuget Package Manager等等抡蛙。

運行一個例子

使用python自帶的IDLE工具巴柿,輸入以下腳本:

from selenium import webdriver # 導(dǎo)入webdriver包
import time
driver = webdriver.Chrome() # 初始化一個谷歌瀏覽器實例:driver
driver.maximize_window() # 最大化瀏覽器 
time.sleep(5) # 暫停5秒鐘
driver.get("https://www.baidu.com") # 通過get()方法,打開一個url站點

API

參考:site-packages/selenium/webdriver/chrome/webdriver.py

創(chuàng)建webdriver對象

from selenium import webdriver
driver = webdriver.Chrome()
content_div = driver.find_element_by_xpath('//*[@id="bd"]/div[4]/div[1]/div/div[2]/div[1]')
print(content_div.text)

driver.execute_script('')
html_doc = driver.page_source

PhantomJS

PhantomJS是一個可編程的無頭瀏覽器.現(xiàn)在已經(jīng)廢棄了垃它,一般通過Chrome等瀏覽器use headless替換它雅任。
無頭瀏覽器:一個完整的瀏覽器內(nèi)核,包括js解析引擎,渲染引擎,請求處理等,但是不包括顯示和用戶交互頁面的瀏覽器笙蒙。
通常無頭瀏覽器可以用于頁面自動化罕扎,網(wǎng)頁監(jiān)控聚唐,網(wǎng)絡(luò)爬蟲等:

頁面自動化測試:希望自動的登陸網(wǎng)站并做一些操作然后檢查結(jié)果是否正常。
網(wǎng)頁監(jiān)控:希望定期打開頁面腔召,檢查網(wǎng)站是否能正常加載杆查,加載結(jié)果是否符合預(yù)期。加載速度如何等臀蛛。
網(wǎng)絡(luò)爬蟲:獲取頁面中使用js來下載和渲染信息亲桦,或者是獲取鏈接處使用js來跳轉(zhuǎn)后的真實地址。

查找元素

方法 功能
find_element_by_id Finds an element by id 根據(jù)id查找元素
find_element_by_xpath Finds an element by xpath. 根據(jù)xpath查找元素
find_element_by_link_text Finds an element by link text.根據(jù)超鏈接文本查找元素
find_element_by_partial_link_text Finds an element by a partial match of its link text.根據(jù)部分鏈接查找元素
find_element_by_name Finds an element by name.根據(jù)元素的name值查找元素
find_element_by_tag_name Finds an element by tag name.根據(jù)元素的tag名查找元素
find_element_by_class_name Finds an element by class name.根據(jù)元素的class name查找元素
find_element_by_css_selector Finds an element by css selector.根據(jù)css selector查找元素

屬性

current_url:獲得當(dāng)前頁面的url
page_source:獲得當(dāng)前頁面的源代碼
current_window_handle:獲得操作當(dāng)前window的句柄
window_handles:獲得當(dāng)前session中的所有window的句柄


>>> from selenium import webdriver
>>> browser = webdriver.Chrome()

DevTools listening on ws://127.0.0.1:12829/devtools/browser/caf2501f-f39f-4792-bfd3-37ecb6b5bf1c
>>> browser.get("http://www.baidu.com")
>>> browser.current_url
'https://www.baidu.com/'
>>> browser.window_handles
['CDwindow-9D2023D530996DB5A116DB6F13CF8C69']
>>> browser.current_window_handle
'CDwindow-9D2023D530996DB5A116DB6F13CF8C69'

基本操作

方法 功能
execute_script Synchronously Executes JavaScript in the current window/frame.以同步的方式執(zhí)行js
execute_async_script Asynchronously Executes JavaScript in the current window/frame.以異步的方式執(zhí)行js
close Closes the current window.關(guān)閉當(dāng)前的窗口
quit Quits the driver and closes every associated window.關(guān)閉所有窗口
maximize_window Maximizes the current window that webdriver is using.最大化當(dāng)前窗口
fullscreen_window Invokes the window manager-specific 'full screen' operation.進入全屏模式
minimize_window Invokes the window manager-specific 'minimize' operation最小化窗口
switch_to 切換到某個地方浊仆,例如:driver.switch_to.window('main')<br />alert = driver.switch_to.alert<br />element = driver.switch_to.active_element
back Goes one step backward in the browser history.
forward Goes one step forward in the browser history.
refresh Refreshes the current page.刷新當(dāng)前頁面
get_cookies Returns a set of dictionaries, corresponding to cookies visible in the current session.獲得當(dāng)前會話中的所有cookies
get_cookie Get a single cookie by name. Returns the cookie if found, None if not.獲得當(dāng)前會話中指定name的cookie
delete_cookie Deletes a single cookie with the given name.
delete_all_cookies Delete all cookies in the scope of the session.
add_cookie Adds a cookie to your current session.
implicitly_wait Sets a sticky timeout to implicitly wait for an element to be found,
set_page_load_timeout 設(shè)置頁面加載超時時間
save_screenshot Saves a screenshot of the current window to a PNG image file.

中間件結(jié)合Selenium

taobao.py

import scrapy
class TaobaoSpider(scrapy.Spider):
    name = 'taobao'
    allowed_domains = ['www.taobao.com']

    def start_requests(self):
        for url in urls:
            req = scrapy.Request(url, callback=self.parse, meta={'use_selenium':True})
            yield req

    def parse(self, response):
        print(response.body)
        pass

middlewares.py

from selenium  import webdriver
from selenium.webdriver.chrome.options import Option

class TaobaoDownloaderMiddleware(object):
    ...
    def process_request(self, request, spider):
        if request.meta.get('use_selenium'):
            print('~~~~我是中間件~~~~~請求經(jīng)過過了~~~~')
            # 設(shè)置無界面運行ChromeDriver
            option = Options()
            option.add_argument('--headless')
            driver = webdriver.Chrome(chrome_options=option)

            # driver = webdriver.Chrome()
            driver.implicitly_wait(15)
            driver.get(request.url)
            # 執(zhí)行js
            js = 'window.scrollTo(0, document.body.scrollHeight)'
            driver.execute_script(js)
            content = driver.page_source
            from scrapy.http import HtmlResponse
            resp = HtmlResponse(request.url, request=request, body=content, encoding='utf-8')
            return resp
        return None

    
    
#  點擊事件
driver=webdriver.Firefox()

driver.get("https://sg.search.yahoo.com/")

searchWhat=driver.find_element_by_id("yschsp")

#獲取id叫做'yschsp'的元素

searchWhat.clear()

#通過clear方法烙肺,可以將輸入框內(nèi)的字符清空,比較保險的做法

searchWhat.send_keys("python")

#通過send_keys方法把'python'傳遞給serchWhat元素氧卧,即id叫做'yschsp'的元素

searchBtn=driver.find_element_by_class_name("sbb")

#獲取id叫做'sbb'的元素,但通常不推薦用class找氏堤,用selector能更精確的找到

searchBtn.click()

#通過click()方法點擊    

settings.py

DOWNLOADER_MIDDLEWARES = {
   'Taobao.middlewares.TaobaoDownloaderMiddleware': 543,
}

顯示等待沙绝、隱式等待和強制等待

  • sleep(): 強制等待搏明,設(shè)置固定休眠時間。 python 的 time 包提供了休眠方法 sleep() 闪檬, 導(dǎo)入 time 包后就可以使用 sleep()星著,進行腳本的執(zhí)行過程進行休眠。
  • implicitly_wait():隱式等待粗悯,也叫智能等待虚循,是 webdirver 提供的一個超時等待。等待一個元素被發(fā)現(xiàn)样傍,或一個命令完成横缔。如果超出了設(shè)置時間的則拋出異常。
  • WebDriverWait():顯示等待衫哥,同樣也是 webdirver 提供的方法茎刚。在設(shè)置時間內(nèi),默認(rèn)每隔一段時間檢測一次當(dāng)前頁面元素是否存在撤逢,如果超過設(shè)置時間檢測不到則拋出異常膛锭。默認(rèn)檢測頻率為0.5s,默認(rèn)拋出異常為:NoSuchElementException

避免被block的多種方式

1. 禁止Cookie

# 在setting中修改cookie為本地禁止
# Disable cookies (enabled by default)
#去除注釋
COOKIES_ENABLED = False

2. 設(shè)置下載延時

#在setting中修改延時時間:3代表爬蟲下載網(wǎng)頁的時間間隔為3s.
DOWNLOAD_DELAY = 3

3. 使用ip池

使用代理IP組成ip池蚊荣,每次爬取可以隨機選擇ip池的ip下載

# 網(wǎng)上獲取代理代理Ip后初狰,在settings中設(shè)置為Ip池:外層通過列表形式存儲,里層通過字典形式存儲互例。
IPPOOL = [
    {'ipaddr':'ip'}
]
"""
在scrapy中奢入,與代理服務(wù)器設(shè)置相關(guān)的下載中間件是HttpProxyMiddleware,同樣在scrapy官方文檔中,HttpProxyMiddleware對應(yīng)的類為
class scrapy.contrib.downloadermiddleware.httpproxy.HttpProxyMiddleware
所以編輯時候需要導(dǎo)入敲霍。
"""


# middlewares.py中:
import random
from Taobao.settings import IPPOOL
from scrapy.contrib.downloadermiddleware.httpproxy import HttpProxyMiddleware

class IPPOOLS(HttpProxyMiddleware):
    def process_request(self, request, spider):
        thisip = random.choice(IPPOOL)
        print('當(dāng)前使用的ip是:'+thisip["ipaddr"])
        request.meta['proxy'] = 'http://' + thisip['ipaddr']
        

setting中設(shè)置如下:

DOWNLOADER_MIDDLEWARES = {
    'scrapy.contrib.downloadermiddleware.httpproxy.HttpProxyMiddleware':123,
    'Taobao.middlewares.IPPOOLS':125,
}

4.使用用戶代理池

與ip代理池不同的是此時我們需要下載中間件是UserAgentMiddleware

首先需要在setting配置文件中設(shè)置好用戶代理池俊马,名稱可以自定義;

setting配置:
UAPOOL = [
    'Mozilla/5.0 (X11; U; Linux x86_64; zh-CN; rv:1.9.2.10) Gecko/20100922 Ubuntu/10.10 (maverick) Firefox/3.6.10',
    'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.71 Safari/537.36',
    'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50',
    'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:2.0.1) Gecko/20100101 Firefox/4.0.1',
    'Opera/9.80 (Windows NT 6.1; U; en) Presto/2.8.131 Version/11.11',
]

中間件中寫上如下代碼:

Middleware.py:
import random
from Taobao.settings import UAPOOL
from scrapy.contrib.downloadermiddleware.useragent import UserAgentMiddleware

class Uamid(UserAgentMiddleware):
    def process_request(self, request, spider):
        thisua = random.choice(UAPOOL)
        print('當(dāng)前使用的user-agent是:'+thisua)
        request.headers.setdefault('User-Agent',thisua)
        
#setting中設(shè)置如下:
DOWNLOADER_MIDDLEWARES = {
    # 讓自定義的中間件優(yōu)先級高于UserAgentMiddleware 或者將2改為None也可以
    'scrapy.contrib.downloadermiddleware.useragent.UserAgentMiddleware':2,
    'Taobao.middlewares.Uamid':1
}

#設(shè)置好之后會通過下載中間件自動的隨機切換用戶代理進行網(wǎng)頁爬取
# 使用第三方庫隨機生成user agent 需要安裝  pip install fake_useragent
from fake_useragent import UserAgent

class Uamid(UserAgentMiddleware):
    ...
    def process_request(self, request, spider):        
        thisua = UserAgent().random
        ...

scrapy-splash動態(tài)爬蟲

安裝Docker

Docker是一個開源的軟件部署解決方案

官方文檔:https://docs.docker.com

中文參考文檔:http://www.docker.org.cn

在Ubuntu中安裝二進制Docker (Install Docker CE from binaries)

優(yōu)點是安裝簡單肩杈,缺點是無服務(wù)柴我、無配置文件,相當(dāng)于Windows平臺中的綠色軟件扩然。

使用安裝腳本自動安裝Docker及其依賴

wget -qO- https://get.docker.com/ | sh

sudo service docker start

docker run hello-world

安裝scrapy-splash

pip install scrapy-splash

配置scrapy-splash

# 渲染服務(wù)的url
SPLASH_URL = 'http://127.0.0.1:8050'

#下載器中間件
DOWNLOADER_MIDDLEWARES = {
    'scrapy_splash.SplashCookiesMiddleware': 800,
    'scrapy_splash.SplashMiddleware': 801,
    'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware': 802,
}
# 去重過濾器
DUPEFILTER_CLASS = 'scrapy_splash.SplashAwareDupeFilter'
# 使用Splash的Http緩存
HTTPCACHE_STORAGE = 'scrapy_splash.SplashAwareFSCacheStorage'

編寫spider

class SplashSpider(scrapy.Spider):
    name = 'scrapy_splash'
    start_urls = [
        'https://movie.douban.com/subject/26752088/'
    ]

    #request需要封裝成SplashRequest
    def start_requests(self):
        urls = ['https://movie.douban.com/subject/26752088/']
        for url in urls:

            yield SplashRequest(url, callback=self.parse)

    def parse(self, response):
        item = SplashTestItem()
        print("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~`")
        #//*[@id="hot-comments"]/div[@class="comment-item"]'
        comments = response.xpath('//*[@id="hot-comments"]/div'
                                  '[@class="comment-item"]')
        for comment in comments:
            #.//span[@class="votes"]/text()
            item['votes'] = comment.xpath('.//span[@class="votes"]/text()'
                                          '').extract_first()
            # .//span[@class="short"]/text()
            item['short'] = comment.xpath('.//span[@class="short"]/text()').extract_first()
            yield item

CrawlSpider類自動爬蟲

簡要說明

CrawlSpider是爬取那些具有一定規(guī)則網(wǎng)站的常用的爬蟲艘儒,它基于Spider并有一些獨特屬性

  • rules: 是Rule對象的集合,用于匹配目標(biāo)網(wǎng)站并排除干擾
  • parse_start_url: 用于爬取起始響應(yīng)夫偶,必須要返回Item界睁,Request中的一個
class Rule(object):

    def __init__(self, link_extractor, callback=None, cb_kwargs=None, follow=None, process_links=None, process_request=identity):
        self.link_extractor = link_extractor
        self.callback = callback
        self.cb_kwargs = cb_kwargs or {}
        self.process_links = process_links
        self.process_request = process_request
        if follow is None:
            self.follow = False if callback else True
        else:
            self.follow = follow
  1. link_extractor 是一個 Link Extractor 對象。 其定義了如何從爬取到的頁面提取鏈接兵拢。

    其中的link_extractor既可以自己定義翻斟,也可以使用已有LinkExtractor類,主要參數(shù)為:

    • allow:滿足括號中“正則表達式”的值會被提取说铃,如果為空访惜,則全部匹配嘹履。
    • deny:與這個正則表達式(或正則表達式列表)不匹配的URL一定不提取。
    • allow_domains:會被提取的鏈接的domains债热。
    • deny_domains:一定不會被提取鏈接的domains砾嫉。
    • restrict_xpaths:使用xpath表達式,和allow共同作用過濾鏈接窒篱。
    • 還有一個類似的restrict_css
  2. callback 是一個callable或string(該spider中同名的函數(shù)將會被調(diào)用)焕刮。 從link_extractor中每獲取到鏈接時將會調(diào)用該函數(shù)。該回調(diào)函數(shù)接受一個response作為其第一個參數(shù)墙杯, 并返回一個包含Item 以及(或) Request 對象(或者這兩者的子類)的列表(list)配并。

  3. 當(dāng)編寫爬蟲規(guī)則時,請避免使用 parse 作為回調(diào)函數(shù)霍转。 由于 CrawlSpider 使用 parse 方法來實現(xiàn)其邏輯荐绝,如果 您覆蓋了 parse 方法,crawl spider 將會運行失敗避消。

  4. cb_kwargs 包含傳遞給回調(diào)函數(shù)的參數(shù)(keyword argument)的字典低滩。

  5. follow 是一個布爾(boolean)值,指定了根據(jù)該規(guī)則從response提取的鏈接是否需要跟進岩喷。 如果callback 為None恕沫, follow 默認(rèn)設(shè)置為 True ,否則默認(rèn)為 False 纱意。

  6. process_links 是一個callable或string(該spider中同名的函數(shù)將會被調(diào)用)婶溯。 從link_extractor中獲取到鏈接列表時將會調(diào)用該函數(shù)。該方法主要用來過濾偷霉。

  7. process_request 是一個callable或string(該spider中同名的函數(shù)將會被調(diào)用)迄委。 該規(guī)則提取到每個request時都會調(diào)用該函數(shù)。該函數(shù)必須返回一個request或者None类少。 (用來過濾request)

CrawlSpider樣例

from scrapy.spiders import CrawlSpider, Rule
from scrapy.linkextractors import LinkExtractor

class MySpider(CrawlSpider):
    name = 'example.com'
    allowed_domains = ['example.com']
    start_urls = ['http://www.example.com']

    rules = (
        # Extract links matching 'category.php' (but not matching 'subsection.php')
        # and follow links from them (since no callback means follow=True by default).
        # 提取匹配 'category.php' (但不匹配 'subsection.php') 的鏈接并跟進鏈接
        #(沒有callback意味著follow默認(rèn)為True)
        Rule(LinkExtractor(allow=('category\.php', ), deny=('subsection\.php', ))),

        # Extract links matching 'item.php' and parse them with the spider's method parse_item
        # 提取匹配 'item.php' 的鏈接并使用spider的parse_item方法進行分析
        Rule(LinkExtractor(allow=('item\.php', )), callback='parse_item'),
    )

    def parse_item(self, response):
        self.logger.info('Hi, this is an item page! %s', response.url)
        item = scrapy.Item()
        item['id'] = response.xpath('//td[@id="item_id"]/text()').re(r'ID: (\d+)')
        item['name'] = response.xpath('//td[@id="item_name"]/text()').extract()
        item['description'] = response.xpath('//td[@id="item_description"]/text()').extract()
        return item

創(chuàng)建Scrapy工程

#scrapy startproject 工程名
scrapy startproject demo4

根據(jù)爬蟲模板生成爬蟲文件

#scrapy genspider -l # 查看可用模板
#scrapy genspider -t 模板名 爬蟲文件名 允許的域名
scrapy genspider -t crawl test sohu.com

增加并發(fā)

并發(fā)是指同時處理的request的數(shù)量叙身。其有全局限制和局部(每個網(wǎng)站)的限制。

Scrapy默認(rèn)的全局并發(fā)限制對同時爬取大量網(wǎng)站的情況并不適用硫狞,因此您需要增加這個值信轿。 增加多少取決于您的爬蟲能占用多少CPU。 一般開始可以設(shè)置為 100 残吩。不過最好的方式是做一些測試财忽,獲得Scrapy進程占取CPU與并發(fā)數(shù)的關(guān)系。 為了優(yōu)化性能泣侮,您應(yīng)該選擇一個能使CPU占用率在80%-90%的并發(fā)數(shù)即彪。

增加全局并發(fā)數(shù):

CONCURRENT_REQUESTS = 100

降低log級別

當(dāng)進行通用爬取時,一般您所注意的僅僅是爬取的速率以及遇到的錯誤活尊。 Scrapy使用 INFO log級別來報告這些信息祖凫。為了減少CPU使用率(及記錄log存儲的要求), 在生產(chǎn)環(huán)境中進行通用爬取時您不應(yīng)該使用 DEBUG log級別琼蚯。 不過在開發(fā)的時候使用 DEBUG 應(yīng)該還能接受。

設(shè)置Log級別:

LOG_LEVEL = 'INFO'

禁止cookies

除非您 真的 需要惠况,否則請禁止cookies。在進行通用爬取時cookies并不需要宁仔, (搜索引擎則忽略cookies)稠屠。禁止cookies能減少CPU使用率及Scrapy爬蟲在內(nèi)存中記錄的蹤跡,提高性能翎苫。

禁止cookies:

COOKIES_ENABLED = False

禁止重試

對失敗的HTTP請求進行重試會減慢爬取的效率权埠,尤其是當(dāng)站點響應(yīng)很慢(甚至失敗)時, 訪問這樣的站點會造成超時并重試多次煎谍。這是不必要的攘蔽,同時也占用了爬蟲爬取其他站點的能力。

禁止重試:

RETRY_ENABLED = False

減小下載超時

如果您對一個非常慢的連接進行爬取(一般對通用爬蟲來說并不重要)呐粘, 減小下載超時能讓卡住的連接能被快速的放棄并解放處理其他站點的能力满俗。

減小下載超時:

DOWNLOAD_TIMEOUT = 15

禁止重定向

除非您對跟進重定向感興趣,否則請考慮關(guān)閉重定向作岖。 當(dāng)進行通用爬取時唆垃,一般的做法是保存重定向的地址,并在之后的爬取進行解析痘儡。 這保證了每批爬取的request數(shù)目在一定的數(shù)量辕万, 否則重定向循環(huán)可能會導(dǎo)致爬蟲在某個站點耗費過多資源。

關(guān)閉重定向:

REDIRECT_ENABLED = False

運行爬蟲

使用runspider命令運行

scrapy runspider some_spider.py

使用crawl命令運行

scrapy crawl spider_name

使用cmdline運行

from scrapy import cmdline

cmdline.execute("scrapy crawl movie".split())

使用CrawlerProcess運行

import scrapy
from scrapy.crawler import CrawlerProcess
from .spiders import spider_a
from .spiders import spider_b
process = CrawlerProcess()

process.crawl(spider_a)
process.crawl(spider_b)
process.start()

暫停沉删,恢復(fù)爬蟲

scrapy crawl somespider -s JOBDIR=crawls/somespider-1

使用中間件實現(xiàn)IP代理池

class MyProxyMidleware(object):
     
    def process_request(self, request, spider):
        request.meta['proxy']  = random.choice(my_proxies.PROXY)

使用中間件實現(xiàn)User-Agent代理池

from . import settings

class RandomUAMiddleware(object):
    def process_request(self, request, spider):
        ua = random.choice(settings.USER_AGENT_LIST)
        print(request.headers)
        if ua:
            request.headers.setdefault('User-Agent', ua)
        print(request.headers)

使用Scrapy登錄網(wǎng)站

class LoginSpider(scrapy.Spider):
    name = 'login'
    allowed_domains = ['127.0.0.1']
 
    def start_requests(self):
        start_urls = ['http://127.0.0.1:8888/login/']
        for url in start_urls:
            request = scrapy.Request(url, callback=self.login)
            yield request
 
    def login(self, response):
        request = FormRequest.from_response(response, formdata={
            'username': 'admin', 'password': 'qianfeng'},
            callback=self.after_login
 
        )
        yield request
 
    def after_login(self, response):
        print(response.text)

使用scrapy—redis實現(xiàn)分布式爬蟲

優(yōu)點:可以充分地利用多個電腦的資源

scrapy本身支持分布式嗎渐尿?
不支持的!7濉砖茸!
為什么不支持呢?
scrapy的url隊列存在哪里脯倚? (單機內(nèi)存)
如何實現(xiàn)分布式呢渔彰?
替換url隊列的存儲介質(zhì) (redis支持分布式的內(nèi)存數(shù)據(jù)庫)
為scrapy做一個新的調(diào)度器(redis),替換scapy的默認(rèn)調(diào)度器, 從而實現(xiàn)分布式功能推正。

scrapy-redis

scrapy-redis是scrapy的一個組件(插件)恍涂,和 scrapy 、redis配合植榕。從而實現(xiàn)支持分布式爬蟲
start_urls= ['http://www.dushu.com' ] # 以前
redis-cli lpush myspider:start_urls 'http://www.dushu.com' # 使用分布式

scrapy-redis是大神們寫的一個scrapy的組件再沧,主要用來負(fù)責(zé)分布式爬蟲的調(diào)度任務(wù)它依賴于Scrapy和redis。

scrapy-redis提供了幾個新的組件(新類)用來補充scrapy不能實現(xiàn)分布式的問題尊残,它們分別是Scheduler炒瘸、Dupefilter淤堵、Pipeline和Spider。

scrapy用類似Python中collection.deque的對象來保存待爬取的urls顷扩,

scrapy-redis用redis數(shù)據(jù)庫來保存待爬取的urls(redis支持分布式拐邪,支持隊列共享)

scrapy-redis.png
  • MasterSpiderstart_urls 中的 urls 構(gòu)造 request,獲取 response
  • MasterSpiderresponse 解析隘截,獲取目標(biāo)頁面的 url, 利用 redis 對 url 去重并生成待爬 request 隊列
  • SlaveSpider 讀取 redis 中的待爬隊列扎阶,構(gòu)造 request
  • SlaveSpider 發(fā)起請求,獲取目標(biāo)頁面的 response
  • Slavespider 解析 response婶芭,獲取目標(biāo)數(shù)據(jù)东臀,寫入生產(chǎn)數(shù)據(jù)庫

redis在爬蟲系統(tǒng)中的作用:

  1. 存儲鏈接
  2. 和scrapy一起工作,redis用來調(diào)度spiders(多個spider共用一個redis隊列犀农,即分布式處理)

充分利用redis結(jié)構(gòu)的作用:

set:set中沒有重復(fù)元素惰赋,利用這個特性,可以用來過濾重復(fù)元素呵哨;

list:實現(xiàn)隊列和棧的功能赁濒;

zset(sorted set):元素需要排序的時候用;

hash:存儲的信息字段比較多時仇穗,可以用hash流部;

使用scrapy-redis實現(xiàn)分布式處理的步驟

創(chuàng)建項目

scrapy  startproject example-project
cd example-project
Scrapy genspider dmoz dmoz.org
scrapy genspider myspider_redis  dmoz.org
scrapy genspider mycrawler_redis dmoz.org

編寫代碼

(這里我們直接使用官方網(wǎng)站的演示代碼,演示分布式的配置纹坐、運行等)

使用scrapy redis枝冀,需要注意以下幾個區(qū)別:

  1. 傳統(tǒng)的spiders中,每個spider類繼承的是scrapy.Spider類或Scrapy.spiders.CrawlSpider類耘子,而分布式寫法每個spider繼承的是scrapy_redis.spiders.RedisSpider或scrapy_redis.spiders.RedisCrawlSpider類果漾。

    from scrapy_redis.spiders import RedisCrawlSpider
    class MyCrawler(RedisCrawlSpider):
        ......
    # 或者
    from scrapy_redis.spiders import RedisSpider
    class MySpider(RedisSpider):
        ...... 
    
  1. 在分布式寫法中,start_urls=[]換成了 redis_key = 'myspider:start_urls'

    class MyCrawler(RedisCrawlSpider):
        """Spider that reads urls from redis queue (myspider:start_urls)."""
        name = 'mycrawler_redis'
        redis_key = 'mycrawler:start_urls'
    
  2. 在分布式寫法中谷誓, allowed_domains = ['dmoz.org'] 換成了

    domain = kwargs.pop('domain', '')
     # Dynamically define the allowed domains list.
        domain = kwargs.pop('domain', '')
        self.allowed_domains = filter(None, domain.split(','))
        super(Myspider1Spider, self).__init__(*args, **kwargs)
    

搭建分布式爬蟲環(huán)境

環(huán)境準(zhǔn)備:4臺服務(wù)器绒障,一個做master,另外3個做slave捍歪。

scrapy户辱、scrapy-redis、redis

master服務(wù)器的配置:

  1. 安裝scrapy糙臼、scrapy-redis庐镐、redis。

  2. 修改master的redis配置文件redis.conf:

    1)將 bind 127.0.0.1 修改為bind 0.0.0.0变逃。(注意防火墻設(shè)置)

    2)重啟redis-server必逆。

  3. 在爬蟲項目中的setting.py文件中添加配置信息:

    REDIS_HOST = 'localhost'
    REDIS_PORT = 6379
    

slave端的配置:

  1. 在爬蟲項目中的setting.py文件中添加配置信息:

    REDIS_URL = 'redis://redis_server ip:6379'
    

master和slave端中共同的配置

在setting.py中啟用redis存儲

ITEM_PIPELINES = {
    'scrapy_redis.pipelines.RedisPipeline': 400,
}

運行分布式爬蟲:爬蟲將處于監(jiān)聽狀態(tài),等待獲取redis隊列中的url

# scrapy runspider myspider_redis.py
scrapy crawl myspider

向redis隊列添加url

redis-cli -h redis_server_ip 
redis-cli> lpush myspider_redis:start_urls http://www.xxxxxx.com/aaa/

案例完整代碼

myspider1.py

# -*- coding: utf-8 -*-
import scrapy
from scrapy_redis.spiders import RedisCrawlSpider
from redis_project.items import RedisProjectItem


class Myspider1Spider(RedisCrawlSpider):
    name = 'myspider1'
    redis_key = 'mycrawler:start_urls'
    # allowed_domains = ['blog.jobbole.com']
    #start_urls = ['http://blog.jobbole.com/all-posts/']

    def __init__(self, *args, **kwargs):
        # Dynamically define the allowed domains list.
        # 命令行可以傳參 scrapy crawl -a domain='blog.jobble.com'
        domain = kwargs.pop('domain', '')
        self.allowed_domains = filter(None, domain.split(','))
        super(Myspider1Spider, self).__init__(*args, **kwargs)

    def parse(self, response):
        links = response.xpath('//a[@class="archive-title"]')
        for link in links:
            item = RedisProjectItem()
            title = link.xpath('./@title').extract_first()
            link = link.xpath('./@href').extract_first()
            item['title'] = title
            item['link'] = link
            yield item


settings.py

REDIS_HOST = '127.0.0.1'
REDIS_PORT = 6379
REDIS_ITEMS_KEY = 'jobbole:posts'
REDIS_URL = 'redis://127.0.0.1:6379'

BOT_NAME = 'redis_project'

SPIDER_MODULES = ['redis_project.spiders']
NEWSPIDER_MODULE = 'redis_project.spiders'

USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit'
'/605.1.15 (KHTML, like Gecko) Version/11.1.1 Safari/605.1.15'

ROBOTSTXT_OBEY = False

ITEM_PIPELINES = {
    'redis_project.pipelines.RedisProjectPipeline': 300,
    'scrapy_redis.pipelines.RedisPipeline': 400,
}

items.py

import scrapy

class RedisProjectItem(scrapy.Item):
    # define the fields for your item here like:
    title = scrapy.Field()
    link = scrapy.Field()

process_redis.py: 用于將redis中數(shù)據(jù)轉(zhuǎn)存到mysql的腳本文件

"""
把redis中的數(shù)據(jù)轉(zhuǎn)存到數(shù)據(jù)庫中
"""
from redis import Redis
import settings
import pymysql
import json


class RedisPipeline(object):

    def __init__(self):
        """
        連接數(shù)據(jù)庫
        """

        self.conn = pymysql.connect(
            host='127.0.0.1',
            port=3306,
            user='root',
            password='123456',
            db='spider',
            charset='utf8'
        )

        self.cursor = self.conn.cursor()
        # 連接redis
        self.rds = Redis('127.0.0.1', 6379)

    def process_item(self):
        while True:
            _, data = self.rds.blpop(settings.REDIS_ITEMS_KEY)
            item = json.loads(data.decode('utf-8'))
            sql = "insert into jobbole(title,link) values (%s,%s)"
            self.cursor.execute(sql, (item['title'], item['link']))
            self.conn.commit()

    def close(self):
        self.cursor.close()
        self.conn.close()


redis_pipelie = RedisPipeline()
redis_pipelie.process_item()
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市名眉,隨后出現(xiàn)的幾起案子粟矿,更是在濱河造成了極大的恐慌,老刑警劉巖损拢,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件陌粹,死亡現(xiàn)場離奇詭異,居然都是意外死亡福压,警方通過查閱死者的電腦和手機申屹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來隧膏,“玉大人,你說我怎么就攤上這事嚷那“恚” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵魏宽,是天一觀的道長腐泻。 經(jīng)常有香客問我,道長队询,這世上最難降的妖魔是什么派桩? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮蚌斩,結(jié)果婚禮上铆惑,老公的妹妹穿的比我還像新娘。我一直安慰自己送膳,他們只是感情好员魏,可當(dāng)我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著叠聋,像睡著了一般撕阎。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上碌补,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天虏束,我揣著相機與錄音,去河邊找鬼厦章。 笑死镇匀,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的闷袒。 我是一名探鬼主播坑律,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了晃择?” 一聲冷哼從身側(cè)響起冀值,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎宫屠,沒想到半個月后列疗,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡浪蹂,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年抵栈,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片坤次。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡古劲,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出缰猴,到底是詐尸還是另有隱情产艾,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布滑绒,位于F島的核電站闷堡,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏疑故。R本人自食惡果不足惜杠览,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望纵势。 院中可真熱鬧踱阿,春花似錦、人聲如沸吨悍。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽育瓜。三九已至葫隙,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間躏仇,已是汗流浹背恋脚。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留焰手,地道東北人糟描。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像书妻,于是被迫代替她去往敵國和親船响。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容