scrapy架構
Scrapy主要組件
1、引擎(Scrapy): 用來處理整個系統的數據流處理, 觸發事務(框架核心)。
2、調度器(Scheduler): 用來接受引擎發過來的請求, 壓入隊列中, 并在引擎再次請求的時候返回. 可以想像成一個URL(抓取網頁的網址或者說是鏈接)的優先隊列, 由它來決定下一個要抓取的網址是什么, 同時去除重復的網址。
3、下載器(Downloader): 用于下載網頁內容, 并將網頁內容返回給蜘蛛(Scrapy下載器是建立在twisted這個高效的異步模型上的)。
4、爬蟲(Spiders): 爬蟲是主要干活的, 用于從特定的網頁中提取自己需要的信息, 即所謂的實體(Item)。用戶也可以從中提取出鏈接,讓Scrapy繼續抓取下一個頁面。
5、項目管道(Pipeline): 負責處理爬蟲從網頁中抽取的實體,主要的功能是持久化實體、驗證實體的有效性、清除不需要的信息。當頁面被爬蟲解析后,將被發送到項目管道,并經過幾個特定的次序處理數據。
6、下載器中間件(Downloader Middlewares): 位于Scrapy引擎和下載器之間的框架,主要是處理Scrapy引擎與下載器之間的請求及響應。
7、爬蟲中間件(Spider Middlewares): 介于Scrapy引擎和爬蟲之間的框架,主要工作是處理蜘蛛的響應輸入和請求輸出。
8、調度中間件(Scheduler Middewares): 介于Scrapy引擎和調度之間的中間件,從Scrapy引擎發送到調度的請求和響應。
安裝
- 首先考慮使用最簡單的方法安裝,可能會有諸多錯誤,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
-
直接使用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/,在網頁中搜索twisted找到其對應的whl包并下載.
下載完成后使用cmd打開windows的命令行窗口,進入whl包所在的文件夾執行如下命令
pip install [whl]
,[whl]是whl包的名字.scrapy依賴lxml包,需要先安裝lxml包.
pip install lxml
即可,再安裝twisted,最后安裝scrapy.安裝完成后使用
scrapy -h
測試是否安裝成功 windows系統安裝完成后運行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 做測試用,反映當前性能,爬蟲速度
scrapy bench
# fetch 幫助我們下載網頁,將網頁源代碼返回(前面是一些日志,后面是源代碼)
scrapy fetch url
#生成爬蟲
scrapy genspider +文件名+網址
# runspider運行爬蟲文件,與crawl的去區別是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交互環境,主要使用這里面的response命令, 例如response.xpath() 括號里直接加xpath路徑
scrapy shell url
#創建項目
scrapy startproject demo
#查看scrapy版本 -v可以輸出依賴庫的版本
scrapy version -v
# view請求Url,把它的網頁源代碼保存成文件,并打開網頁
scrapy view http://www.example.com/some/page.html
#查看爬蟲列表
scrapy list
#check檢查錯誤
scrapy check
# 運行(crawl)
scrapy crawl +爬蟲名稱
#使用 EDITOR 中設定的編輯器編輯給定的spider
#該命令僅僅是提供一個快捷方式。開發者可以自由選擇其他工具或者IDE來編寫調試spider。
scrapy edit spider1
settings.py配置
# USER_AGENT 設置用戶代理
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'
#設置是否遵守robots協議
ROBOTSTXT_OBEY = True
# 設置抓取中文顯示編碼
FEED_EXPORT_ENCODING = 'utf-8'
# pipelines激活
ITEM_PIPELINES = {
'BtMovie.pipelines.BtmoviePipeline': 300,
}
scrapy抓取網站
一般需要四個步驟
- 創建一個爬蟲項目
- 定義Item容器
- 編寫爬蟲,提取數據
- 存儲內容
創建項目
在開始爬取之前,您必須創建一個新的Scrapy項目。 進入您打算存儲代碼的目錄中,運行下列命令:
scrapy startproject tutorial
該命令將會創建包含下列內容的 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
: 項目的設置文件. -
tutorial/spiders/
: 放置spider代碼的目錄.
定義Item
Item 是保存爬取到的數據的容器;其使用方法和python字典類似, 并且提供了額外保護機制來避免拼寫錯誤導致的未定義字段錯誤。
類似在ORM中做的一樣,您可以通過創建一個 scrapy.Item
類, 并且定義類型為 scrapy.Field
的類屬性來定義一個Item。
首先根據需要從toscrape.com獲取到的數據對item進行建模。 我們需要從quotes.py
中獲取名字,url,以及網站的描述。 對此,在item中定義相應的字段。編輯 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是用戶編寫用于從單個網站(或者一些網站)爬取數據的類。
其包含了一個用于下載的初始URL,如何跟進網頁中的鏈接以及如何分析頁面中的內容, 提取生成 item 的方法。
為了創建一個Spider,您必須繼承 scrapy.Spider
類, 且定義以下三個屬性:
-
name
: 用于區別Spider。 該名字必須是唯一的,您不可以為不同的Spider設定相同的名字。 -
start_urls
: 包含了Spider在啟動時進行爬取的url列表。 因此,第一個被獲取到的頁面將是其中之一。 后續的URL則從初始的URL獲取到的數據中提取。 -
parse()
是spider的一個方法。 被調用時,每個初始URL完成下載后生成的Response
對象將會作為唯一的參數傳遞給該函數。 該方法負責解析返回的數據(response data),提取數據(生成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)
#回調函數
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:
#爬蟲的名字就是quotes.py中的name
scrapy crawl quotes
scrapy crawl quotes --nolog
不生成日志文件
或者在spiders目錄下創建run.py
寫入
from scrapy import cmdline
# -o表示文件名 -t表示文件格式
cmdline.execute("scrapy crawl news -o news.json -t json".split())
提取Item
Selectors選擇器簡介
從網頁中提取數據有很多方法。Scrapy使用了一種基于 XPath 和 CSS 表達式機制: Scrapy Selectors。 關于selector和其他提取機制的信息請參考 Selector文檔 。
這里給出XPath表達式的例子及對應的含義:
-
/html/head/title
: 選擇HTML文檔中head
標簽內的title
元素 -
/html/head/title/text()
: 選擇上面提到的title
元素的文字 -
//td
: 選擇所有的<td>
元素 -
//div[@class="mine"]
: 選擇所有具有class="mine"
屬性的div
元素
上邊僅僅是幾個簡單的XPath例子,XPath實際上要比這遠遠強大的多。 如果您想了解的更多,我們推薦 這篇XPath教程 。
為了配合XPath,Scrapy除了提供了 Selector
之外,還提供了方法來避免每次從response中提取數據時生成selector的麻煩。
Selector有四個基本的方法(點擊相應的方法可以看到詳細的API文檔):
-
xpath()
: 傳入xpath表達式,返回該表達式所對應的所有節點的selector list列表 。 -
css()
: 傳入CSS表達式,返回該表達式所對應的所有節點的selector list列表. -
extract()
: 序列化該節點為unicode字符串并返回list。 -
re()
: 根據傳入的正則表達式對數據進行提取,返回unicode字符串list列表。
在Shell中嘗試Selector選擇器
為了介紹Selector的使用方法,接下來我們將要使用內置的 Scrapy shell 。Scrapy Shell需要您預裝好IPython(一個擴展的Python終端)。
您需要進入項目的根目錄,執行下列命令來啟動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
當shell載入后,您將得到一個包含response數據的本地 response
變量。輸入 response.body
將輸出response的包體, 輸出 response.headers
可以看到response的包頭。
更為重要的是,當輸入 response.selector
時, 您將獲取到一個可以用于查詢返回數據的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">→</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'
我們可以通過這段代碼選擇該頁面中網站列表里所有 <li>
元素:
response.xpath('//ul/li')
網站的描述:
response.xpath('//ul/li/text()').extract()
網站的標題:
response.xpath('//ul/li/a/text()').extract()
以及網站的鏈接:
response.xpath('//ul/li/a/@href').extract()
string()和text()的區別,以原生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標簽下的 b標簽不再被解析 第一個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']
總結:text()獲得的總是一個list,而string()直接獲得一個字符串
提取數據
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
保存爬取到的數據
scrapy crawl quotes -o items.json
? 在這樣小規模的項目中,這種存儲方式已經足夠。 如果需要對爬取到的item做更多更為復雜的操作,可以編寫 piplines.py
文件。
爬取下一頁
我們既然要爬取下一頁,那我們首先要分析鏈接格式,找到下一頁的鏈接。
<li class="next">
<a >下一頁 ?</a>
</li>
那到底如何讓蜘蛛自動的判斷、并爬取下一頁、下一頁的內容呢?我們可以這樣來做,我們每爬一頁就用css選擇器來查詢,是否存在下一頁鏈接,存在:則爬取下一頁鏈接http://lab.scrapyd.cn/page/*/ ,然后把下一頁鏈接提交給當前爬取的函數,繼續爬取,繼續查找下一頁,知道找不到下一頁,說明所有頁面已經爬完,那結束爬蟲。
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方法會自動幫我們實現。方法2
follow還可以不用獲取url字符串,只需要傳入一個selector 。This selector should extract necessary attributes.方法3
<a>標簽有一個簡寫,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
傳遞參數
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
傳遞參數,這些參數傳遞到爬蟲類的__init__
中
scrapy crawl quotes -o quotes-humor.json -a tag=humor
#url = 'http://quotes.toscrape.com/tag/humor'
傳遞參數demo
編寫爬蟲文件 job.py
#框架已經自動創建以下內容
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 是爬行的起始網址,可以定義多個,用逗號隔開
#如果沒有特別的回調函數,該方法是處理acrapy爬蟲爬行到的網頁響應(response)額默認方法,可以對響應進行處理冰返回處理后的數據,也負責鏈接的跟蹤。
#對初始內容進行修改
import scrapy
from jobproject.items import JobprojectItem
class JobSpider(scrapy.Spider):
name = 'job'
allowed_domains = ['51job.com']
start_urls = ['http://51job.com/']
#重新初始化方法,并設置參數place,這樣運行時候就可以加上-a place參數值賦值了
def __init__(self,place,*args,**kwargs):
super().__init__(*args,**kwargs)
#設置默認地址為杭州
if place is None:
self.place = '杭州'
else:
self.place=place
#重寫start_requests方法,將起始網址設置未從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默認方法實現生成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
數據的保存
將數據保存為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()
# 使用系統自帶
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()
將數據存儲到mysql和mongodb
pipelines.py
class MySQLPipeline(object):
"""
pymyql
"""
def __init__(self):
"""
連接數據庫
"""
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):
"""
把每條數據插入數據庫
"""
sql = "insert into qcwy(name) value(%s)"
self.cursor.execute(sql, (item['name'],))
self.conn.commit()
return item
def close_spider(self,spider):
"""
關閉數據庫連接
"""
self.cursor.close()
self.conn.close()
class MongoDBPipeline(object):
"""
pymongo
"""
def __init__(self):
"""
連接mongo
"""
#創建一個客戶端
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應用程序測試的工具。Selenium測試直接運行在瀏覽器中,就像真正的用戶在操作一樣。支持的瀏覽器包括IE(7, 8, 9, 10, 11),Mozilla Firefox,Safari,Google Chrome,Opera等。這個工具的主要功能包括:測試與瀏覽器的兼容性——測試你的應用程序看是否能夠很好得工作在不同瀏覽器和操作系統之上。測試系統功能——創建回歸測試檢驗軟件功能和用戶需求。支持自動錄制動作和自動生成 .Net、Java、Perl等不同語言的測試腳本。
Selenium安裝
pip install selenium
安裝driver
Firefox瀏覽器需安裝geckdriver,Chrome瀏覽器需要安裝chromedriver,IE瀏覽器要安裝IEdriver。
可以在npm淘寶鏡像中下載。下載chrome驅動 解壓放到python解釋器所在目錄
什么是npm?
npm(node package manager)node的包管理工具.簡單地說npm就是一個基于nodejs的包管理器,它管理的是javascript。
? 舉例來說:如果我們在開發過程中使用jquery,那么是不是要引入jquery,你可能會下載這個jquery.js文件,然后在代碼中<script src="jquery.js"></script>是吧 。如果使用 npm ,那么就方便了,直接在npm下使用命令:$ npm install jquery;就自動下載了。
在遠端有一個npm服務器,里面有很多別人寫的代碼,我們可以直接使用npm下載使用。 同時你也可以把自己寫的代碼推送到npm 服務器,讓別人使用。
包管理工具還有Ubuntu的apt-get,CentOS的yum,微軟的Nuget Package Manager等等。
運行一個例子
使用python自帶的IDLE工具,輸入以下腳本:
from selenium import webdriver # 導入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
創建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是一個可編程的無頭瀏覽器.現在已經廢棄了,一般通過Chrome等瀏覽器use headless替換它。
無頭瀏覽器:一個完整的瀏覽器內核,包括js解析引擎,渲染引擎,請求處理等,但是不包括顯示和用戶交互頁面的瀏覽器。
通常無頭瀏覽器可以用于頁面自動化,網頁監控,網絡爬蟲等:
頁面自動化測試:希望自動的登陸網站并做一些操作然后檢查結果是否正常。
網頁監控:希望定期打開頁面,檢查網站是否能正常加載,加載結果是否符合預期。加載速度如何等。
網絡爬蟲:獲取頁面中使用js來下載和渲染信息,或者是獲取鏈接處使用js來跳轉后的真實地址。
查找元素
方法 | 功能 |
---|---|
find_element_by_id | Finds an element by id 根據id查找元素 |
find_element_by_xpath | Finds an element by xpath. 根據xpath查找元素 |
find_element_by_link_text | Finds an element by link text.根據超鏈接文本查找元素 |
find_element_by_partial_link_text | Finds an element by a partial match of its link text.根據部分鏈接查找元素 |
find_element_by_name | Finds an element by name.根據元素的name值查找元素 |
find_element_by_tag_name | Finds an element by tag name.根據元素的tag名查找元素 |
find_element_by_class_name | Finds an element by class name.根據元素的class name查找元素 |
find_element_by_css_selector | Finds an element by css selector.根據css selector查找元素 |
屬性
current_url:獲得當前頁面的url
page_source:獲得當前頁面的源代碼
current_window_handle:獲得操作當前window的句柄
window_handles:獲得當前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.以同步的方式執行js |
execute_async_script | Asynchronously Executes JavaScript in the current window/frame.以異步的方式執行js |
close | Closes the current window.關閉當前的窗口 |
quit | Quits the driver and closes every associated window.關閉所有窗口 |
maximize_window | Maximizes the current window that webdriver is using.最大化當前窗口 |
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.刷新當前頁面 |
get_cookies | Returns a set of dictionaries, corresponding to cookies visible in the current session.獲得當前會話中的所有cookies |
get_cookie | Get a single cookie by name. Returns the cookie if found, None if not.獲得當前會話中指定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 | 設置頁面加載超時時間 |
save_screenshot | Saves a screenshot of the current window to a PNG image file. |
中間件結合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('~~~~我是中間件~~~~~請求經過過了~~~~')
# 設置無界面運行ChromeDriver
option = Options()
option.add_argument('--headless')
driver = webdriver.Chrome(chrome_options=option)
# driver = webdriver.Chrome()
driver.implicitly_wait(15)
driver.get(request.url)
# 執行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方法,可以將輸入框內的字符清空,比較保險的做法
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(): 強制等待,設置固定休眠時間。 python 的 time 包提供了休眠方法 sleep() , 導入 time 包后就可以使用 sleep(),進行腳本的執行過程進行休眠。
- implicitly_wait():隱式等待,也叫智能等待,是 webdirver 提供的一個超時等待。等待一個元素被發現,或一個命令完成。如果超出了設置時間的則拋出異常。
- WebDriverWait():顯示等待,同樣也是 webdirver 提供的方法。在設置時間內,默認每隔一段時間檢測一次當前頁面元素是否存在,如果超過設置時間檢測不到則拋出異常。默認檢測頻率為0.5s,默認拋出異常為:NoSuchElementException
避免被block的多種方式
1. 禁止Cookie
# 在setting中修改cookie為本地禁止
# Disable cookies (enabled by default)
#去除注釋
COOKIES_ENABLED = False
2. 設置下載延時
#在setting中修改延時時間:3代表爬蟲下載網頁的時間間隔為3s.
DOWNLOAD_DELAY = 3
3. 使用ip池
使用代理IP組成ip池,每次爬取可以隨機選擇ip池的ip下載
# 網上獲取代理代理Ip后,在settings中設置為Ip池:外層通過列表形式存儲,里層通過字典形式存儲。
IPPOOL = [
{'ipaddr':'ip'}
]
"""
在scrapy中,與代理服務器設置相關的下載中間件是HttpProxyMiddleware,同樣在scrapy官方文檔中,HttpProxyMiddleware對應的類為
class scrapy.contrib.downloadermiddleware.httpproxy.HttpProxyMiddleware
所以編輯時候需要導入。
"""
# 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('當前使用的ip是:'+thisip["ipaddr"])
request.meta['proxy'] = 'http://' + thisip['ipaddr']
setting中設置如下:
DOWNLOADER_MIDDLEWARES = {
'scrapy.contrib.downloadermiddleware.httpproxy.HttpProxyMiddleware':123,
'Taobao.middlewares.IPPOOLS':125,
}
4.使用用戶代理池
與ip代理池不同的是此時我們需要下載中間件是UserAgentMiddleware
首先需要在setting配置文件中設置好用戶代理池,名稱可以自定義;
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('當前使用的user-agent是:'+thisua)
request.headers.setdefault('User-Agent',thisua)
#setting中設置如下:
DOWNLOADER_MIDDLEWARES = {
# 讓自定義的中間件優先級高于UserAgentMiddleware 或者將2改為None也可以
'scrapy.contrib.downloadermiddleware.useragent.UserAgentMiddleware':2,
'Taobao.middlewares.Uamid':1
}
#設置好之后會通過下載中間件自動的隨機切換用戶代理進行網頁爬取
# 使用第三方庫隨機生成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動態爬蟲
安裝Docker
Docker是一個開源的軟件部署解決方案
中文參考文檔:http://www.docker.org.cn
在Ubuntu中安裝二進制Docker (Install Docker CE from binaries)
優點是安裝簡單,缺點是無服務、無配置文件,相當于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
# 渲染服務的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是爬取那些具有一定規則網站的常用的爬蟲,它基于Spider并有一些獨特屬性
- rules: 是Rule對象的集合,用于匹配目標網站并排除干擾
- parse_start_url: 用于爬取起始響應,必須要返回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
-
link_extractor
是一個 Link Extractor 對象。 其定義了如何從爬取到的頁面提取鏈接。其中的link_extractor既可以自己定義,也可以使用已有LinkExtractor類,主要參數為:
- allow:滿足括號中“正則表達式”的值會被提取,如果為空,則全部匹配。
- deny:與這個正則表達式(或正則表達式列表)不匹配的URL一定不提取。
- allow_domains:會被提取的鏈接的domains。
- deny_domains:一定不會被提取鏈接的domains。
- restrict_xpaths:使用xpath表達式,和allow共同作用過濾鏈接。
- 還有一個類似的restrict_css
callback
是一個callable或string(該spider中同名的函數將會被調用)。 從link_extractor中每獲取到鏈接時將會調用該函數。該回調函數接受一個response作為其第一個參數, 并返回一個包含Item
以及(或)Request
對象(或者這兩者的子類)的列表(list)。當編寫爬蟲規則時,請避免使用
parse
作為回調函數。 由于CrawlSpider
使用parse
方法來實現其邏輯,如果 您覆蓋了parse
方法,crawl spider 將會運行失敗。cb_kwargs
包含傳遞給回調函數的參數(keyword argument)的字典。follow
是一個布爾(boolean)值,指定了根據該規則從response提取的鏈接是否需要跟進。 如果callback
為None,follow
默認設置為True
,否則默認為False
。process_links
是一個callable或string(該spider中同名的函數將會被調用)。 從link_extractor中獲取到鏈接列表時將會調用該函數。該方法主要用來過濾。process_request
是一個callable或string(該spider中同名的函數將會被調用)。 該規則提取到每個request時都會調用該函數。該函數必須返回一個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默認為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
創建Scrapy工程
#scrapy startproject 工程名
scrapy startproject demo4
根據爬蟲模板生成爬蟲文件
#scrapy genspider -l # 查看可用模板
#scrapy genspider -t 模板名 爬蟲文件名 允許的域名
scrapy genspider -t crawl test sohu.com
增加并發
并發是指同時處理的request的數量。其有全局限制和局部(每個網站)的限制。
Scrapy默認的全局并發限制對同時爬取大量網站的情況并不適用,因此您需要增加這個值。 增加多少取決于您的爬蟲能占用多少CPU。 一般開始可以設置為 100
。不過最好的方式是做一些測試,獲得Scrapy進程占取CPU與并發數的關系。 為了優化性能,您應該選擇一個能使CPU占用率在80%-90%的并發數。
增加全局并發數:
CONCURRENT_REQUESTS = 100
降低log級別
當進行通用爬取時,一般您所注意的僅僅是爬取的速率以及遇到的錯誤。 Scrapy使用 INFO
log級別來報告這些信息。為了減少CPU使用率(及記錄log存儲的要求), 在生產環境中進行通用爬取時您不應該使用 DEBUG
log級別。 不過在開發的時候使用 DEBUG
應該還能接受。
設置Log級別:
LOG_LEVEL = 'INFO'
禁止cookies
除非您 真的 需要,否則請禁止cookies。在進行通用爬取時cookies并不需要, (搜索引擎則忽略cookies)。禁止cookies能減少CPU使用率及Scrapy爬蟲在內存中記錄的蹤跡,提高性能。
禁止cookies:
COOKIES_ENABLED = False
禁止重試
對失敗的HTTP請求進行重試會減慢爬取的效率,尤其是當站點響應很慢(甚至失敗)時, 訪問這樣的站點會造成超時并重試多次。這是不必要的,同時也占用了爬蟲爬取其他站點的能力。
禁止重試:
RETRY_ENABLED = False
減小下載超時
如果您對一個非常慢的連接進行爬取(一般對通用爬蟲來說并不重要), 減小下載超時能讓卡住的連接能被快速的放棄并解放處理其他站點的能力。
減小下載超時:
DOWNLOAD_TIMEOUT = 15
禁止重定向
除非您對跟進重定向感興趣,否則請考慮關閉重定向。 當進行通用爬取時,一般的做法是保存重定向的地址,并在之后的爬取進行解析。 這保證了每批爬取的request數目在一定的數量, 否則重定向循環可能會導致爬蟲在某個站點耗費過多資源。
關閉重定向:
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()
暫停,恢復爬蟲
scrapy crawl somespider -s JOBDIR=crawls/somespider-1
使用中間件實現IP代理池
class MyProxyMidleware(object):
def process_request(self, request, spider):
request.meta['proxy'] = random.choice(my_proxies.PROXY)
使用中間件實現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登錄網站
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實現分布式爬蟲
優點:可以充分地利用多個電腦的資源
scrapy本身支持分布式嗎?
不支持的!!!
為什么不支持呢?
scrapy的url隊列存在哪里? (單機內存)
如何實現分布式呢?
替換url隊列的存儲介質 (redis支持分布式的內存數據庫)
為scrapy做一個新的調度器(redis),替換scapy的默認調度器, 從而實現分布式功能。
scrapy-redis
scrapy-redis是scrapy的一個組件(插件),和 scrapy 、redis配合。從而實現支持分布式爬蟲
start_urls= ['http://www.dushu.com' ] # 以前
redis-cli lpush myspider:start_urls 'http://www.dushu.com' # 使用分布式
scrapy-redis是大神們寫的一個scrapy的組件,主要用來負責分布式爬蟲的調度任務它依賴于Scrapy和redis。
scrapy-redis提供了幾個新的組件(新類)用來補充scrapy不能實現分布式的問題,它們分別是Scheduler、Dupefilter、Pipeline和Spider。
scrapy用類似Python中collection.deque的對象來保存待爬取的urls,
scrapy-redis用redis數據庫來保存待爬取的urls(redis支持分布式,支持隊列共享)
-
MasterSpider
對start_urls
中的 urls 構造request
,獲取response
-
MasterSpider
將response
解析,獲取目標頁面的 url, 利用 redis 對 url 去重并生成待爬request
隊列 -
SlaveSpider
讀取 redis 中的待爬隊列,構造request
-
SlaveSpider
發起請求,獲取目標頁面的response
-
Slavespider
解析response
,獲取目標數據,寫入生產數據庫
redis在爬蟲系統中的作用:
- 存儲鏈接
- 和scrapy一起工作,redis用來調度spiders(多個spider共用一個redis隊列,即分布式處理)
充分利用redis結構的作用:
set:set中沒有重復元素,利用這個特性,可以用來過濾重復元素;
list:實現隊列和棧的功能;
zset(sorted set):元素需要排序的時候用;
hash:存儲的信息字段比較多時,可以用hash;
使用scrapy-redis實現分布式處理的步驟
創建項目
scrapy startproject example-project
cd example-project
Scrapy genspider dmoz dmoz.org
scrapy genspider myspider_redis dmoz.org
scrapy genspider mycrawler_redis dmoz.org
編寫代碼
(這里我們直接使用官方網站的演示代碼,演示分布式的配置、運行等)
使用scrapy redis,需要注意以下幾個區別:
-
傳統的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): ......
-
在分布式寫法中,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'
-
在分布式寫法中, 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)
搭建分布式爬蟲環境
環境準備:4臺服務器,一個做master,另外3個做slave。
scrapy、scrapy-redis、redis
master服務器的配置:
安裝scrapy、scrapy-redis、redis。
-
修改master的redis配置文件redis.conf:
1)將 bind 127.0.0.1 修改為bind 0.0.0.0。(注意防火墻設置)
2)重啟redis-server。
-
在爬蟲項目中的setting.py文件中添加配置信息:
REDIS_HOST = 'localhost' REDIS_PORT = 6379
slave端的配置:
-
在爬蟲項目中的setting.py文件中添加配置信息:
REDIS_URL = 'redis://redis_server ip:6379'
master和slave端中共同的配置
在setting.py中啟用redis存儲
ITEM_PIPELINES = {
'scrapy_redis.pipelines.RedisPipeline': 400,
}
運行分布式爬蟲:爬蟲將處于監聽狀態,等待獲取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中數據轉存到mysql的腳本文件
"""
把redis中的數據轉存到數據庫中
"""
from redis import Redis
import settings
import pymysql
import json
class RedisPipeline(object):
def __init__(self):
"""
連接數據庫
"""
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()