Scrapy是採用Python開發的一個快速、高層次的屏幕抓取和web抓取框架,用於抓取採集web站點信息並從頁面中提取結構化的數據。
Scrapy用途廣泛,可以用於數據挖掘、監測和自動化測試等。
Scrapy吸引人的地方在於它是一個框架,任何人都可以根據需求方便的修改。
它也提供了多種類型爬蟲的基類,如BaseSpider、sitemap爬蟲等,最新版本又提供了web2.0爬蟲的支持。
Scrapy is a fast high-level web crawling and web scraping framework, used to crawl websites and extract structured data from their pages.
It can be used for a wide range of purposes, from data mining to monitoring and automated testing.
For more information including a list of features check the Scrapy homepage at: http://scrapy.org
Scrapy 官網:https://scrapy.org
Scrapy 插件:https://pypi.python.org/pypi/Scrapy
Scratch,是抓取的意思,這個Python的爬蟲框架叫Scrapy,大概也是這個意思吧,就叫它:小刮刮吧。
Scrapy 使用了 Twisted異步網絡庫來處理網絡通訊,其設計整體架構大致如下圖:
Scrapy主要包括了以下組件:
- 引擎(Scrapy)
用來處理整個系統的數據流處理, 觸發事務(框架核心)
- 調度器(Scheduler)
用來接受引擎發過來的請求, 壓入隊列中, 並在引擎再次請求的時候返回. 可以想像成一個URL(抓取網頁的網址或者說是鏈接)的優先隊列, 由它來決定下一個要抓取的網址是什麼, 同時去除重複的網址
- 下載器(Downloader)
用於下載網頁內容, 並將網頁內容返回給蜘蛛(Scrapy下載器是建立在twisted這個高效的異步模型上的)
- 爬蟲(Spiders)
爬蟲是主要幹活的, 用於從特定的網頁中提取自己需要的信息, 即所謂的實體(Item)。用戶也可以從中提取出鏈接,讓Scrapy繼續抓取下一個頁面
- 項目管道(Pipeline)
負責處理爬蟲從網頁中抽取的實體,主要的功能是持久化實體、驗證實體的有效性、清除不需要的信息。當頁面被爬蟲解析後,將被髮送到項目管道,並經過幾個特定的次序處理數據。
- 下載器中間件(Downloader Middlewares)
位於Scrapy引擎和下載器之間的框架,主要是處理Scrapy引擎與下載器之間的請求及響應。
- 爬蟲中間件(Spider Middlewares)
介於Scrapy引擎和爬蟲之間的框架,主要工作是處理蜘蛛的響應輸入和請求輸出。
- 調度中間件(Scheduler Middewares)
介於Scrapy引擎和調度之間的中間件,從Scrapy引擎發送到調度的請求和響應。
Scrapy運行流程大概如下:
- 引擎從調度器中取出一個鏈接(URL)用於接下來的抓取
- 引擎把URL封裝成一個請求(Request)傳給下載器
- 下載器把資源下載下來,並封裝成應答包(Response)
- 爬蟲解析Response
- 解析出實體(Item),則交給實體管道進行進一步的處理
- 解析出的是鏈接(URL),則把URL交給調度器等待抓取
Scrapy 安裝
因爲python3並不能完全支持Scrapy,因此爲了完美運行Scrapy,我們使用python2.7來編寫和運行Scrapy。
pip install Scrapy # python2.7
pip3 install Scrapy # python3.6
驗證安裝成功:
1
2
3
4
5
6
7
|
$ python
Python 2.7.13 (v2.7.13:a06454b1afa1, Dec 17 2016, 12:39:47)
[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin
Type
"help"
,
"copyright"
,
"credits"
or
"license"
for
more
information.
>>>
import
scrapy
>>> scrapy
<module
'scrapy'
from
'/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/scrapy/__init__.pyc'
>
|
Scrapy 使用
1、創建項目 mimvp_proxy_python_scrapy
運行命令:
1
2
3
4
5
|
$
which
scrapy
/Library/Frameworks/Python
.framework
/Versions/3
.6
/bin/scrapy
$
$
cd
MimvpProxyDemo
/PythonScrapy/
$ scrapy startproject mimvp_proxy_python_scrapy
|
2、查看目錄結構
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
$ tree
.
|____mimvp_proxy_python_scrapy
| |____mimvp_proxy_python_scrapy
| | |______init__.py
| | |______pycache__
| | |____items.py
| | |____middlewares.py
| | |____pipelines.py
| | |____settings.py
| | |____spiders
| | | |______init__.py
| | | |______pycache__
| |____scrapy.cfg
|
導入Eclipse,查看自動創建的目錄結構:
目錄文件說明:
- scrapy.cfg 項目的配置信息,主要爲Scrapy命令行工具提供一個基礎的配置信息
- items.py 設置數據存儲模板,用於結構化數據,如:Django的Model
- pipelines 數據處理行爲,如:一般結構化的數據持久化
- settings.py 配置文件,如:遞歸的層數、併發數,延遲下載等爬蟲相關的配置
- spiders 爬蟲目錄,如:創建文件、編寫爬蟲規則
注意:一般創建爬蟲文件時,以網站域名命名
3、Scrapy 編寫爬蟲
在spiders目錄中新建 mimvp_spider.py 文件
示例代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
#!/usr/bin/env python
# -*- coding:utf-8 -*-
#
# mimvp.com
# 2009.10.1
import
scrapy
class
MimvpSpider(scrapy.spiders.Spider):
name
=
"mimvp"
allowed_domains
=
[
"mimvp.com"
]
start_urls
=
[
"http://mimvp.com"
,
]
def
parse(
self
, response):
# print(response, type(response))
# from scrapy.http.response.html import HtmlResponse
# print(response.body_as_unicode())
mimvp_url
=
response.url
# 爬取時請求的url
body
=
response.body
# 返回網頁內容
unicode_body
=
response.body_as_unicode()
# 返回的html unicode編碼
print
(
"mimvp_url : "
+
str
(mimvp_url))
print
(
"body : "
+
str
(body))
print
(
"unicode_body : "
+
str
(unicode_body))
|
爲了創建一個Spider,必須繼承 scrapy.Spider
類, 且定義以下三個屬性:
name
: 用於區別Spider。 該名字必須是唯一的,您不可以爲不同的Spider設定相同的名字。
start_urls
: 包含了Spider在啓動時進行爬取的url列表。 因此,第一個被獲取到的頁面將是其中之一。 後續的URL則從初始的URL獲取到的數據中提取。
parse()
是spider的一個方法。 被調用時,每個初始URL完成下載後生成的 Response
對象將會作爲唯一的參數傳遞給該函數。 該方法負責解析返回的數據(response data),提取數據(生成item)以及生成需要進一步處理的URL的 Request
對象。
詳細說明:
1. 爬蟲文件需要定義一個類,並繼承scrapy.spiders.Spider,即類定義 MimvpSpider(scrapy.spiders.Spider)
2. 必須定義name,即爬蟲名,如果沒有name,會報錯,因爲源碼中是這樣定義的:
1
2
3
4
5
6
7
8
|
def
__init__(
self
, name
=
None
,
*
*
kwargs):
if
name
is
not
None
:
self
.name
=
name
elif
not
getattr
(
self
,
'name'
,
None
):
raise
ValueError(
"%s must have a name"
%
type
(
self
).__name__)
self
.__dict__.update(kwargs)
if
not
hasattr
(
self
,
'start_urls'
):
self
.start_urls
=
[]
|
3. 編寫函數parse,這裏需要注意的是,該函數名不能改變,因爲Scrapy源碼中默認callback函數的函數名就是parse;
4. 定義需要爬取的url,放在列表中,因爲可以爬取多個url,Scrapy源碼是一個For循環,從上到下爬取這些url,使用生成器迭代將url發送給下載器下載url的html。源碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
|
def
start_requests(
self
):
cls
=
self
.__class__
if
method_is_overridden(
cls
, Spider,
'make_requests_from_url'
):
for
url
in
self
.start_urls:
yield
self
.make_requests_from_url(url)
else
:
for
url
in
self
.start_urls:
yield
Request(url, dont_filter
=
True
)
def
make_requests_from_url(
self
, url):
""" This method is deprecated. """
return
Request(url, dont_filter
=
True
)
|
Request 類源碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
class
Request(object_ref):
def
__init__(
self
, url, callback
=
None
, method
=
'GET'
, headers
=
None
, body
=
None
,
cookies
=
None
, meta
=
None
, encoding
=
'utf-8'
, priority
=
0
,
dont_filter
=
False
, errback
=
None
, flags
=
None
):
self
._encoding
=
encoding
# this one has to be set first
self
.method
=
str
(method).upper()
self
._set_url(url)
self
._set_body(body)
assert
isinstance
(priority,
int
),
"Request priority not an integer: %r"
%
priority
self
.priority
=
priority
assert
callback
or
not
errback,
"Cannot use errback without a callback"
self
.callback
=
callback
self
.errback
=
errback
self
.cookies
=
cookies
or
{}
self
.headers
=
Headers(headers
or
{}, encoding
=
encoding)
self
.dont_filter
=
dont_filter
self
._meta
=
dict
(meta)
if
meta
else
None
self
.flags
=
[]
if
flags
is
None
else
list
(flags)
|
4、Scrapy 運行
格式:scrapy crawl + 爬蟲名 –nolog # 不顯示日誌
進入項目 mimvp_proxy_python_scrapy 目錄下,運行命令:
1
2
|
$
cd
mimvp_proxy_python_scrapy/
$ scrapy crawl mimvp --nolog
|
運行結果:
$ scrapy crawl mimvp --nolog
mimvp_url : http://mimvp.com
body : b'<!DOCTYPE html>\n<html lang="zh-CN">\n<head>\n<meta charset="utf-8">\n<meta http-equiv="Content-Type" content="text/html; charset=utf-8">\n<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">\n<meta http-equiv="Cache-Control" content="no-transform">
..........
5、Scrapy 查詢語法
當我們爬取大量的網頁,如果自己寫正則匹配,會很麻煩,也很浪費時間,令人欣慰的是,scrapy內部支持更簡單的查詢語法,幫助我們去html中查詢我們需要的標籤和標籤內容以及標籤屬性。
下面逐一進行介紹:
- 查詢子子孫孫中的某個標籤(以div標籤爲例)://div
- 查詢兒子中的某個標籤(以div標籤爲例):/div
- 查詢標籤中帶有某個class屬性的標籤://div[@class=’c1′]即子子孫孫中標籤是div且class=‘c1’的標籤
- 查詢標籤中帶有某個class=‘c1’並且自定義屬性name=‘alex’的標籤://div[@class=’c1′][@name=’alex’]
- 查詢某個標籤的文本內容://div/span/text() 即查詢子子孫孫中div下面的span標籤中的文本內容
- 查詢某個屬性的值(例如查詢a標籤的href屬性)://a/@href
示例代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
def
parse(
self
, response):
# 分析頁面
# 找到頁面中符合規則的內容(校花圖片),保存
# 找到所有的a標籤,再訪問其他a標籤,一層一層的搞下去
hxs
=
HtmlXPathSelector(response)
#創建查詢對象
# 如果url是 http://www.xiaohuar.com/list-1-\d+.html
if
re.match(
'http://www.xiaohuar.com/list-1-\d+.html'
, response.url):
#如果url能夠匹配到需要爬取的url,即本站url
items
=
hxs.select(
'//div[@class="item_list infinite_scroll"]/div'
)
#select中填寫查詢目標,按scrapy查詢語法書寫
for
i
in
range
(
len
(items)):
src
=
hxs.select(
'//div[@class="item_list infinite_scroll"]/div[%d]//div[@class="img"]/a/img/@src'
%
i).extract()
#查詢所有img標籤的src屬性,即獲取校花圖片地址
name
=
hxs.select(
'//div[@class="item_list infinite_scroll"]/div[%d]//div[@class="img"]/span/text()'
%
i).extract()
#獲取span的文本內容,即校花姓名
school
=
hxs.select(
'//div[@class="item_list infinite_scroll"]/div[%d]//div[@class="img"]/div[@class="btns"]/a/text()'
%
i).extract()
#校花學校
if
src:
ab_src
=
"http://www.xiaohuar.com"
+
src[
0
]
#相對路徑拼接
file_name
=
"%s_%s.jpg"
%
(school[
0
].encode(
'utf-8'
), name[
0
].encode(
'utf-8'
))
#文件名,因爲python27默認編碼格式是unicode編碼,因此我們需要編碼成utf-8
file_path
=
os.path.join(
"/Users/wupeiqi/PycharmProjects/beauty/pic"
, file_name)
urllib.urlretrieve(ab_src, file_path)
|
注:urllib.urlretrieve(ab_src, file_path) ,接收文件路徑和需要保存的路徑,會自動去文件路徑下載並保存到我們指定的本地路徑。
6、遞歸爬取網頁
上述代碼僅僅實現了一個url的爬取,如果該url的爬取的內容中包含了其他url,而我們也想對其進行爬取,那麼如何實現遞歸爬取網頁呢?
示例代碼:
1
2
3
4
5
|
# 獲取所有的url,繼續訪問,並在其中尋找相同的url
all_urls
=
hxs.select(
'//a/@href'
).extract()
for
url
in
all_urls:
if
url.startswith(
'http://www.xiaohuar.com/list-1-'
):
yield
Request(url, callback
=
self
.parse)
|
即通過yield生成器向每一個url發送request請求,並執行返回函數parse,從而遞歸獲取校花圖片和校花姓名學校等信息。
注:可以修改settings.py 中的配置文件,以此來指定「遞歸」的層數,如: DEPTH_LIMIT = 1
7、Scrapy 設置代理爬取網頁
Python Scrapy 設置代理有兩種方式,使用時兩種方式選擇一種即可
方式1: 直接在代碼裏設置,如 MimvpSpider ——> start_requests
方式2: 通過 middlewares + settings.py 配置文件設置,步驟:
2.1 middlewares.py 添加代理類 ProxyMiddleware,並添加代理
2.2 settings.py 開啓 DOWNLOADER_MIDDLEWARES,並且添加 'mimvp_proxy_python_scrapy.middlewares.ProxyMiddleware': 100,
方式1:直接在代碼裏設置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
#!/usr/bin/env python
# -*- coding:utf-8 -*-
#
# Python scrapy 支持 http、https
#
# 米撲代理示例:
# http://proxy.mimvp.com/demo2.php
#
# 米撲代理購買:
# http://proxy.mimvp.com
#
# mimvp.com
# 2009.10.1
# Python Scrapy 設置代理有兩種方式,使用時兩種方式選擇一種即可
# 方式1: 直接在代碼裏設置,如 MimvpSpider ——> start_requests
# 方式2: 通過 middlewares + settings.py 配置文件設置,步驟:
# 2.1 middlewares.py 添加代理類 ProxyMiddleware,並添加代理
# 2.2 settings.py 開啓 DOWNLOADER_MIDDLEWARES,並且添加 'mimvp_proxy_python_scrapy.middlewares.ProxyMiddleware': 100,
import
scrapy
class
MimvpSpider(scrapy.spiders.Spider):
name
=
"mimvp"
allowed_domains
=
[
"mimvp.com"
]
start_urls
=
[
"http://proxy.mimvp.com/exist.php"
,
"https://proxy.mimvp.com/exist.php"
,
]
## 代理設置方式1:直接在代理裏設置
def
start_requests(
self
):
urls
=
[
"http://proxy.mimvp.com/exist.php"
,
"https://proxy.mimvp.com/exist.php"
,
]
for
url
in
urls:
meta_proxy
=
""
if
url.startswith(
"http://"
):
meta_proxy
=
"http://180.96.27.12:88"
# http代理
elif
url.startswith(
"https://"
):
meta_proxy
=
"http://109.108.87.136:53281"
# https代理
yield
scrapy.Request(url
=
url, callback
=
self
.parse, meta
=
{
'proxy'
: meta_proxy})
def
parse(
self
, response):
mimvp_url
=
response.url
# 爬取時請求的url
body
=
response.body
# 返回網頁內容
print
(
"mimvp_url : "
+
str
(mimvp_url))
print
(
"body : "
+
str
(body))
|
方式2:配置文件裏設置
a) middlewares.py 文件裏,添加代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
## 代理設置方式2: 通過 middlewares + settings.py 配置文件設置
## mimvp custom by yourself
class
ProxyMiddleware(
object
):
def
process_request(
self
,request,spider):
if
request.url.startswith(
"http://"
):
request.meta[
'proxy'
|