- 框架:具有很強的通用性,且封裝了一些通用實作方法的專案模板
scrapy(異步框架):- 高性能的網路請求
- 高性能的資料決議
- 高性能的持久化存盤
- 高性能的全站資料爬取
- 高性能的深度爬取
- 高性能的分布式
Scrapy環境安裝
IOS和Linux
pip install scrapy
windows
a. pip3 install wheel
b. 下載twisted http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted
# Twisted?17.1.0?cp35?cp35m?win_amd64.whl; Python是3.5版本的就選擇cp35下載
c. 進入下載目錄,執行 pip3 install Twisted?17.1.0?cp35?cp35m?win_amd64.whl
# 安裝失敗可能是這個檔案的版本導致的,即使Python版本都是對的,可以重新下載一個32位的試試
# 還安裝失敗的話就下載其python版本的,總有一個能成功
d. pip3 install pywin32
e. pip3 install scrapy
安裝完成后,輸入``scrapy`測驗一下,出現如下圖顯示,即安裝成功,

Scrapy的基本使用
創建工程
-
scrapy startprojct proNmamecd proNmame進入到工程目錄下執行爬蟲檔案
proName # 工程名字
spiders # 爬蟲包(檔案夾)
__init__.py
__init__.py
items.py
middlewares.py
pipelines.py
settings.py # 創建好的工程的組態檔
scrapy.cfg # scrapy的組態檔,不用修改
創建爬蟲檔案
- 創建爬蟲檔案是py源檔案
scrapy genspider spiderName www.xxx.com網址后期可以修改- 在
spiders包下創建一個py檔案
- 在
# -*- coding: utf-8 -*-
import scrapy
class FirstSpider(scrapy.Spider): # scrapy.Spider所有爬蟲類的父類
# name表示的爬蟲檔案的名稱,當前爬蟲檔案的唯一標識
name = 'first'
# 允許的域名,通常會注釋掉
# allowed_domains = ['www.xx.com']
# 起始的url串列,最開始要爬的網址串列
# 作用:可以將內部的串列元素進行get請求的發送
start_urls = ['http://www.sougou.com/','www.baidu.com']
# 呼叫parse方法決議資料,方法呼叫的次數由start_urls串列元素個數決定的
def parse(self, response): # response表示一個回應物件,
pass
基本配置
-
UA偽裝
-
robots協議的不遵從
在
settings.py中將ROBOTSTXT_OBEY = True修改為False -
指定日志等級
在
settings.py中添加LOG_LEVEL = 'ERROR'
執行工程
-
scrapy crawl spiderName -
執行工程是不展示日志檔案
scrapy crawl spiderName --nolog這種方式下程式報錯,不會展示;設定好日志等級后直接執行工程即可,
資料決議
-
response.xpath('xpath運算式') -
與
etree的不同之處:取文本/屬性:回傳的是一個
Selector物件,文本資料是存盤在該物件中Selector物件[0].extract()回傳字串Selector物件.extract_first()回傳字串Selector物件.extract()回傳串列
常用操作
- 如果串列只有一個元素用
Selector物件.extract_first(),回傳字串 - 如果串列有多個元素
Selector物件.extract(),回傳串列,串列里裝的是字串
spiderName.py檔案
# -*- coding: utf-8 -*-
import scrapy
class DuanziSpider(scrapy.Spider):
name = 'duanzi'
# allowed_domains = ['www.xx.com']
start_urls = ['https://duanziwang.com/']
def parse(self, response):
article_list = response.xpath('/html/body/section/div/div/main/article') # 基于xpath運算式決議
for article in article_list:
title = article.xpath('./div[1]/h1/a/text()')[0] # 回傳一個Selector物件
# <Selector xpath='./div[1]/h1/a/text()' data='https://www.cnblogs.com/Golanguage/p/關于健康養生、延年益壽的生活諺語_段子網收錄最新段子'>
title = article.xpath('./div[1]/h1/a/text()')[0].extract() # 回傳字串
# 關于健康養生、延年益壽的生活諺語_段子網收錄最新段子
title = article.xpath('./div[1]/h1/a/text()').extract_first() # 回傳字串
# 關于健康養生、延年益壽的生活諺語_段子網收錄最新段子
title = article.xpath('./div[1]/h1/a/text()').extract() # 回傳串列
# ['關于健康養生、延年益壽的生活諺語_段子網收錄最新段子']
print(title)
break
持久化存盤
基于終端指令的持久化存盤
-
只可以將parse方法的回傳值存盤到指定后綴的文本檔案中
指定后綴:
'json', 'jsonlines', 'jl', 'csv', 'xml', 'marshal', 'pickle',通常用csv指令
scrapy crawl spiderName -o filePath
案例:將文本資料持久化存盤
# -*- coding: utf-8 -*-
import scrapy
class DuanziSpider(scrapy.Spider):
name = 'duanzi'
# allowed_domains = ['www.xx.com']
start_urls = ['https://duanziwang.com/']
# 基于終端指令的持久化存盤
def parse(self, response):
article_list = response.xpath('/html/body/section/div/div/main/article') # 基于xpath運算式決議
all_data = https://www.cnblogs.com/Golanguage/p/[]
for article in article_list:
title = article.xpath('./div[1]/h1/a/text()').extract_first()
content = article.xpath('./div[2]/p//text()').extract()
content = ''.join(content)
dic = {
"title": title,
"content": content
}
all_data.append(dic)
return all_data
# 終端指令
# scrapy crawl spiderName -o duanzi.csv
基于管道的持久化存盤
? scrapy建議使用管道持久化存盤
實作流程
-
資料決議(
spiderName .py) -
實體化item型別物件(
items.py)在
items.py的item類中定義相關的屬性fieldNmae = scrapy.Field() -
將決議的資料存盤封裝到item型別的物件中(
spiderName .py)item['fileName'] = value給item物件的fieldNmae屬性賦值 -
將item物件提交給(
spiderName .py)yield item將item提交給優先級最高的管道 -
在管道中接收item,可以將item中存盤的資料進行任意形式的持久化存盤(
pipelines.py)process_item():負責接收item物件且對其進行持久化存盤 -
在組態檔
settings.py中開啟管道機制找到如下代碼,取消注釋
ITEM_PIPELINES = { # 300表示的是優先級,數值越小,優先級越高 'duanziPro.pipelines.DuanziproPipeline': 300, }
案例:將文本資料持久化存盤
按上述在settings.py找到管道代碼,取消注釋,
spiderName .py
# -*- coding: utf-8 -*-
import scrapy
from duanziPro.items import DuanziproItem
class DuanziSpider(scrapy.Spider):
name = 'duanzi'
# allowed_domains = ['www.xx.com']
start_urls = ['https://duanziwang.com/']
# 基于管道的持久化存盤
def parse(self, response):
article_list = response.xpath('/html/body/section/div/div/main/article') # 基于xpath運算式決議
for article in article_list:
title = article.xpath('./div[1]/h1/a/text()').extract_first()
content = article.xpath('./div[2]/pre/code//text()').extract()
content = ''.join(content)
print(content)
# 實體化item物件
item = DuanziproItem()
# 通過中括號的形式訪問屬性給其賦值
item['title'] = title
item['content'] = content
# 向管道提交item
yield item
items.py
import scrapy
class DuanziproItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
# 使用固有屬性定義了兩個屬性
# Field是一個萬能資料型別
title = scrapy.Field()
content = scrapy.Field()
pipelines.py
class DuanziproPipeline(object):
# 重寫父類的該方法:該方法只會在爬蟲開始的時候執行一次
fp = None
# 打開檔案
def open_spider(self, spider):
print('open spider')
self.fp = open('./duanzi.txt', 'w', encoding='utf-8')
# 關閉檔案
def close_spider(self, spider):
print('close spider')
self.fp.close()
# 接收爬蟲檔案回傳item物件,process_item方法每呼叫一次可接收一個item物件
# item引數:接收到的某一個item物件
def process_item(self, item, spider):
# 取值
title = item['title']
content = item['content']
self.fp.write(title + ":" + content + "\n")
return item
管道存盤細節處理
-
管道檔案中的管道類表示的是什么?
一個管道類對應的就是一種存盤形式(文本檔案,資料庫)
如果想要實作資料備份,則需要使用多個管道類(多種存盤形式:MySQL,Redis)
-
process_item中的
retutn item:將item傳遞給下一個即將被執行(按照組態檔中ITEM_PIPELINES得權重排序)的管道類
存盤到MySQL
在pipelines.py中添加如下代碼
import pymysql
class MysqlPipeline(object):
conn = None
cursor = None
def open_spider(self, spider):
self.conn = pymysql.Connect(host='127.0.0.1', port=3306, user='root', password='123', db='spider',
charset='utf8')
def process_item(self, item, spider):
# 取值
title = item['title']
content = item['content']
self.cursor = self.conn.cursor()
# sql陳述句
sql = 'insert into duanzi values ("%s","%s")' % (title, content)
try:
self.cursor.execute(sql)
self.conn.commit()
except Exception as e:
print(e)
self.conn.rollback()
return item
def close_spider(self, spider):
self.cursor.close()
self.conn.close()
在settings.py中將MysqlPipeline類注冊到ITEM_PIPELINES中
ITEM_PIPELINES = {
# 300表示的是優先級,數值越小,優先級越高
'duanziPro.pipelines.DuanziproPipeline': 300,
'duanziPro.pipelines.MysqlPipeline': 301,
}
存盤到Redis
-
因為redis有的版本不支持存盤字典,下載2.10.6版本
pip install redis==2.10.6
在pipelines.py中添加如下代碼
from redis import Redis
class RedisPipeline(object):
conn = None
def open_spider(self, spider):
self.conn = Redis(host='127.0.0.1', port=6379, password='yourpassword')
def process_item(self, item, spider):
self.conn.lpush('duanziList', item)
# 報錯:因為redis有的版本不支持存盤字典,pip install redis==2.10.6
在settings.py中將RedisPipeline類注冊到ITEM_PIPELINES中
ITEM_PIPELINES = {
# 300表示的是優先級,數值越小,優先級越高
'duanziPro.pipelines.DuanziproPipeline': 300,
'duanziPro.pipelines.RedisPipeline': 301,
}
手動發送請求
-
可以在start_urls這個串列中添加url,但是比較繁瑣
-
get請求發送
yield scrapy.Request(url,callback)- url:指定好請求的url
- callback:callback指定的回呼函式一定會被執行(資料決議)
-
post請求發送
yield scrapy.FormRequest(url,callback,formdata)- formdata存放請求引數,字典型別
-
父類中start_requests請求發送的原理
# 簡單模擬父類的方法,主要看yield
def start_requests(self):
for url in self.start_urls:
# 發起get請求
yield scrapy.Request(url=url,callback=self.parse)
# 發起post請求,formdata存放請求引數
yield scrapy.FormRequest(url=url,callback=self.parse,formdata=https://www.cnblogs.com/Golanguage/p/{})
代碼實作
-
主要是在
spiderName .py中使用遞回方法,且明確遞回結束的條件;使用父類yield實作全站爬取
# -*- coding: utf-8 -*-
import scrapy
from duanziPro.items import DuanziproItem
class DuanziSpider(scrapy.Spider):
name = 'duanzi'
# allowed_domains = ['www.xx.com']
start_urls = ['https://duanziwang.com/']
# 手動請求的發送,對其他頁碼的資料進行請求操作
# 定義通用url模板
url = "https://duanziwang.com/page/%d/"
pageNum = 2
def parse(self, response):
article_list = response.xpath('/html/body/section/div/div/main/article') # 基于xpath運算式決議
all_data = https://www.cnblogs.com/Golanguage/p/[]
for article in article_list:
title = article.xpath('./div[1]/h1/a/text()').extract_first()
content = article.xpath('./div[2]/pre/code//text()').extract()
content = ''.join(content)
# 實體化item物件
item = DuanziproItem()
# 通過中括號的形式訪問屬性給其賦值
item['title'] = title
item['content'] = content
# 向管道提交item
yield item
if self.pageNum < 5:
new_url = format(self.url%self.pageNum)
self.pageNum += 1
# 遞回實作全站資料爬取,callback指定決議的方法
yield scrapy.Request(url=new_url, callback=self.parse)
- 在
pipelines.py中實作資料持久化存盤
class DuanziproPipeline(object):
# 重寫父類的該方法:該方法只會在爬蟲開始的時候執行一次
fp = None
def open_spider(self, spider):
print('open spider')
self.fp = open('./duanzi.txt', 'w', encoding='utf-8')
# 關閉fp
def close_spider(self, spider):
print('close spider')
self.fp.close()
# 接收爬蟲檔案回傳item物件,process_item方法每呼叫一次可接收一個item物件
# item引數:接收到的某一個item物件
def process_item(self, item, spider):
# 取值
title = item['title']
content = item['content']
self.fp.write(title + ":" + content + "\n")
# 將item轉交給下一個即將被執行的管道類
return item
- 在
settings.py中開啟管道類
ITEM_PIPELINES = {
# 300表示的是優先級,數值越小,優先級越高
'duanziPro.pipelines.DuanziproPipeline': 300,
}
yield在scrapy中的使用
-
向管道中提交item物件
yield item -
手動請求發送
yield scrapy.Request(url,callback)
五大核心組件
-
引擎(Scrapy Engine)
處理整個系統的資料流,觸發事物(框架核心),
-
調度器(Scheduer)
用來接收引擎發過來的請求,壓入佇列中,并在引擎再次請求的時候回傳,
-
下載器(Downloader)
用于下載網頁內容,并將網頁內容回傳給蜘蛛(Scrapy下載器是建立在twisted這個高效模型上的),
-
爬蟲(Spiders)
爬蟲主要是干活的,用于從特定的網頁中提取自己需要的資訊,即所謂的物體(item),用戶也可以從中提取出鏈接,讓Scrapy繼續抓取下一個頁面
-
管道(item Pipeline)
負責處理爬蟲從網頁抽取的物體,主要的功能是持久化物體、驗證物體的有效性、清除不需要的資訊,當頁面被爬蟲決議后,將被發送到專案管道,并經過幾個特定的次序處理資料,
五大核心組件的作業流程

當執行爬蟲檔案時,5大核心組件就在作業了
首先執行爬蟲檔案spider,spider的作用是
(1)決議(2)發請求,原始的url存盤在于spider中
1:當spider執行的時候,首先對起始的url發送請求,將起始url封裝成請求物件
2:將請求物件傳遞給引擎
3:引擎將請求物件傳遞給調度器(內部含有佇列和過濾器兩個機制),調度器將請求存盤在佇列(先進先出)中
4:調度器從佇列中調度出url的相應物件再將請求傳遞給引擎
5:引擎將請求物件通過下載中間件發送給下載器
6:下載器拿到請求到互聯網上去下載
7:互聯網將下載好的資料封裝到回應物件給到下載器
8:下載器將回應物件通過下載中間件發送給引擎
9:引擎將封裝了資料的回應物件回傳給spider類parse方法中的response物件
10:spider中的parse方法被呼叫,response就有了回應值
11:在spider的parse方法中進行決議代碼的撰寫;
(1)會決議出另外一批url,(2)會決議出相關的文本資料
12: 將決議拿到的資料封裝到item中
13:item將封裝的文本資料提交給引擎
14:引擎將資料提交給管道進行持久化存盤(一次完整的請求資料)
15:如果parder方法中決議到的另外一批url想繼續提交可以繼續手動進行發請求
16:spider將這批請求物件封裝提交給引擎
17:引擎將這批請求物件發配給調度器
16:這批url通過調度器中過濾器過濾掉重復的url存盤在調度器的佇列中
17:調度器再將這批請求物件進行請求的調度發送給引擎
引擎作用:
1:處理流資料 2:觸發事物
引擎根據相互的資料流做判斷,根據拿到的流資料進行下一步組件中方法的呼叫
下載中間件: 位于引擎和下載器之間,可以攔截請求和回應物件;攔截到請求和回應物件后可以
篡改頁面內容和請求和回應頭資訊,
爬蟲中間件:位于spider和引擎之間,也可以攔截請求和回應物件,不常用,
請求傳參
-
作用
實作深度爬取,
-
深度爬取
爬取的資料不在同一張頁面中,
-
在進行手動發送請求的時候,可以將一個meta字典傳遞給callback指定的回呼函式
-
yield scrapy.Request(url,callback,meta={}) -
在回呼函式中接收meta
response.meta['key']將meta字典中key對應的value值取出
-
案例:電影名字和簡介爬取
- 案例地址:4567電影網電影分類
spiderName.py
# -*- coding: utf-8 -*-
import scrapy
from moviePro.items import MovieproItem
class MovieSpider(scrapy.Spider):
name = 'movie'
# allowed_domains = ['www.xxx.com']
start_urls = ['https://www.4567kan.com/frim/index1.html']
# 通用url
url = 'https://www.4567kan.com/frim/index1-%d.html'
pagNum = 2
# 決議首頁的資料,爬取電影名稱和簡介
def parse(self, response):
li_list = response.xpath('/html/body/div[1]/div/div/div/div[2]/ul/li')
for li in li_list:
mv_name = li.xpath('./div/a/@title').extract_first()
item = MovieproItem()
item['name'] = mv_name
detail_url = "https://www.4567kan.com/" + li.xpath('./div/a/@href').extract_first()
yield scrapy.Request(url=detail_url, callback=self.parse_detail, meta={'item': item})
# meta 就是一個字典,可以將字典傳給callback指定的回呼函式,實作請求傳參
# 全站資訊的爬取,測驗前4頁
if self.pagNum < 5:
new_url = format(self.url % self.pagNum)
self.pagNum += 1
yield scrapy.Request(url=new_url,callback=self.parse)
# 自定義決議方法,決議詳情頁電影簡介
def parse_detail(self, response):
desc = response.xpath('/html/body/div[1]/div/div/div/div[2]/p[5]/span[3]//text()').extract_first()
# 接收請求傳參傳遞過來的item
item = response.meta['item']
item['desc'] = desc
yield item
中間件
爬蟲中間件
位于spider和引擎之間,也可以攔截請求和回應物件,不常用,
下載中間件(推薦)
位于引擎和下載器之間,可以攔截請求和回應物件;攔截到請求和回應物件后可以
篡改頁面內容和請求和回應頭資訊,
作用:攔截所有請求和回應
為什么要攔截請求?
-
UA偽裝(篡改請求頭資訊)
process_request()方法中,request.headers['User-Agent'] ="請求頭資訊" -
設定代理
process_exception()方法中,request.meta['proxy'] = 'https://ip:prot'return request
工程專案中middlewares.py就是中間件,
from scrapy import signals
import random
# UA池
user_agent_list = [
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 "
"(KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1",
"Mozilla/5.0 (X11; CrOS i686 2268.111.0) AppleWebKit/536.11 "
"(KHTML, like Gecko) Chrome/20.0.1132.57 Safari/536.11",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.6 "
"(KHTML, like Gecko) Chrome/20.0.1092.0 Safari/536.6",
"Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.6 "
"(KHTML, like Gecko) Chrome/20.0.1090.0 Safari/536.6",
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.1 "
"(KHTML, like Gecko) Chrome/19.77.34.5 Safari/537.1",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/536.5 "
"(KHTML, like Gecko) Chrome/19.0.1084.9 Safari/536.5",
"Mozilla/5.0 (Windows NT 6.0) AppleWebKit/536.5 "
"(KHTML, like Gecko) Chrome/19.0.1084.36 Safari/536.5",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 "
"(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
"Mozilla/5.0 (Windows NT 5.1) AppleWebKit/536.3 "
"(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_0) AppleWebKit/536.3 "
"(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 "
"(KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 "
"(KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 "
"(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 "
"(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/536.3 "
"(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 "
"(KHTML, like Gecko) Chrome/19.0.1061.0 Safari/536.3",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.24 "
"(KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24",
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/535.24 "
"(KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24"
]
PROXY_HTTP = ["ip:port"]
PROXY_HTTPS = ["ip:port"]
# 下載中間件
class MiddleproDownloaderMiddleware(object):
def process_request(self, request, spider):
"""
攔截正常請求
:param request: 攔截到的正常請求
:param spider: 爬蟲類實體化的物件
:return:
"""
# UA偽裝
request.headers['User-Agent'] = random.choice(user_agent_list)
return None
def process_response(self, request, response, spider):
"""
process_response函式:攔截所有的回應
:param request: 回應對應的請求
:param response: 攔截到的回應
:param spider: 爬蟲類實體化的物件
:return: 回傳處理后的回應
"""
return response
def process_exception(self, request, exception, spider):
"""
攔截發生例外的請求;對例外請求進行修正,讓其變成正常請求
:param request: 攔截到的發生例外的請求物件
:param exception: 攔截到的例外資訊
:param spider: 爬蟲類實體化的物件
:return: 將修正后的請求物件進行重新發送
"""
# 代理操作
if request.url.split(":")[0] == 'http':
request.meta['proxy'] = "http:{}".format(random.choice(PROXY_HTTP))
else:
request.meta['proxy'] = "https:{}".format(random.choice(PROXY_HTTPS))
return request # 將修正后的請求物件進行重新發送
為什么要攔截回應?
- 篡改回應資料
案例:網易新聞資料爬取
-
實作流程:
1,決議出5個板塊對應的url
2,對5個板塊的url發起請求
3,獲取板塊的頁面原始碼資料
? 問題:資料為動態加載,原始碼資料中沒有新聞標題和詳情頁的url
? 解決:將回應資料進行篡改,改成包含動態加載的資料
-
selenium幫助我們捕獲到包含了動態加載的回應資料
selenium在scrapy中的使用
- 實體化一個瀏覽器物件(爬蟲檔案中)
- 在中間件process_response中進行selenium后續的操作
- 在爬蟲檔案的爬蟲類沖重寫一個closed(self,spider),關閉瀏覽器
在settings.py基本設定一下,打開下載中間件
spiderNmae.py
# -*- coding: utf-8 -*-
import scrapy
from selenium import webdriver
from wangyiPro.items import WangyiproItem
class WangyiSpider(scrapy.Spider):
name = 'wangyi'
# allowed_domains = ['www.xx.com']
start_urls = ['https://news.163.com/']
# 5個板塊頁面的url
model_urls = []
# 實體化瀏覽器物件
bro = webdriver.Chrome(executable_path=r'D:\Reptile\jupyter\onceagain\爬蟲\Scrap框架\chromedriver.exe')
# 資料決議,決議5個板塊對應頁面url
def parse(self, response):
li_list = response.xpath('//*[@id="index2016_wrap"]/div[1]/div[2]/div[2]/div[2]/div[2]/div/ul/li')
index = [3, 4, 6, 7, 8]
for i in index:
model_url = li_list[i].xpath('./a/@href').extract_first()
self.model_urls.append(model_url)
# 對板塊url發請求,捕獲每個頁面的原始碼資料
for url in self.model_urls:
yield scrapy.Request(url=url, callback=self.parse_model)
# 決議標題和新聞詳情頁的url
def parse_model(self, response):
div_list = response.xpath('/html/body/div[1]/div[3]/div[4]/div[1]/div/div/ul/li/div/div')
for div in div_list:
title = div.xpath('./a/img/@alt').extract_first()
new_detail_url = div.xpath('./a/@href').extract_first()
if new_detail_url:
item = WangyiproItem()
item['title'] = title
# 對新聞的詳情頁發請求,決議出新聞的內容
yield scrapy.Request(url=new_detail_url, callback=self.parse_new_detail, meta={'item': item})
# 決議新聞內容
def parse_new_detail(self, response):
item = response.meta['item']
content = response.xpath('//*[@id="endText"]//text()').extract()
content = ''.join(content)
# print(content)
item['content'] = content
yield item
# 整個程式結束時呼叫一次:父類的方法
def closed(self, spider):
self.bro.quit()
middlewares.py
from time import sleep
class WangyiproDownloaderMiddleware(object):
# 攔截回應,篡改指定回應物件的回應資料
def process_response(self, request, response, spider):
# 獲取5個板塊對應的url
model_urls = spider.model_urls
bro = spider.bro
if request.url in model_urls: # 成立之后定位到的response就是某一個板塊對應的response
# 指定回應資料的篡改
# 引數body就是回應資料
bro.get(request.url)
sleep(1)
page_text = bro.page_source # 作為新的回應資料,包含動態加載資料源
return HtmlResponse(url=request.url, body=page_text, encoding='utf-8', request=request)
else:
return response
items.py
import scrapy
class WangyiproItem(scrapy.Item):
title = scrapy.Field()
content = scrapy.Field()
Scrapy爬大文本資料
大文本資料就是量級大的二進制資料,如圖片,壓縮包,音頻,視頻...
-
爬蟲檔案中將二進制資源的url進行爬取和決議,將其存盤到item中向管道提交
-
在管道檔案中指定對應的管道類
父類:
from scrapy.pipelines.images import ImagesPipeline組態檔中進行如下操作
# 自動創建一個指定的檔案夾 IMAGES_STORE = './imgLib'
案例:校花圖片的爬取
自定義一個關于ImagesPipeline該父類的管道類,在pipelines.py中重寫如下三個方法
from scrapy.pipelines.images import ImagesPipeline
import scrapy
class ImgproPipeline(ImagesPipeline):
# 發起請求
def get_media_requests(self, item, info):
imgSrc = https://www.cnblogs.com/Golanguage/p/item['imgSrc']
# 請求傳參,將meta字典傳遞給了file_path這個方法
yield scrapy.Request(url=imgSrc, meta={'item': item})
# 定制get_media_request請求到資料持久化存盤的路徑(檔案夾路徑+檔案名稱)
def file_path(self, request, response=None, info=None):
# 通過request.meta接收請求傳參傳遞過來的meta字典
imgName = request.meta['item']['imgName']
return imgName
# 如果組態檔中沒指定檔案夾
# return '檔案夾/%s.jpg' % (image_guid)
def item_completed(self, results, item, info):
return item
CrawlSpider
全站資料爬取
-
CrawlSpider就是Spider的一個子類 -
創建一個基于
CrawlSpider爬蟲檔案scrapy genspider -t crawl spiderName www.xxx.com -
LinkExtractor(allow=r'正則運算式'):鏈接提取器- 作用:可以根據指定的指定的規則(allow)進行鏈接提取
-
Rule(link,callback,follow=True):規則決議器link:鏈接提取
callback:回呼函式,字串反射呼叫函式,決議資料
follow=True:將鏈接提取器 繼續作用到 鏈接提取器提取到的鏈接的對應頁面中
-
作用:
1.將鏈接提取器提取到的鏈接進行請求發送(get)請求發送
2.請求到的資料根據指定的規則進行資料決議
-
-
深度爬取
手動發送請求決議資料,請求傳參和LinkExtractor,Rule一起使用實作深度爬取
Rule(LinkExtractor(allow=r'正則運算式提取指定url'),callback='函式名',follow=True(提取全站頁碼url)/Flase(提取當前葉頁碼url))
使用鏈接提取器提取詳情頁的url實作深度爬取
案例:陽光問政
spiderName.py中代碼
- 問題:實體化在持久化存盤后無法實作資料一一對應的匯總
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
# 實體化了兩個item物件
from sunPro.items import TitleItem, ContentItem
class SunSpider(CrawlSpider):
name = 'sun'
# allowed_domains = ['www.xxx.com']
start_urls = ['http://wz.sun0769.com/political/index/politicsNewest?id=1&page=1']
# 根據正則提取全站頁碼
link = LinkExtractor(allow=r'id=1&page=\d+')
# 提取標題對應的詳情頁鏈接
link_detail = LinkExtractor(allow=r'politics/index\?id=\d+')
rules = (
Rule(link, callback='parse_item', follow=True),
Rule(link_detail, callback='parse_detail', follow=False),
)
def parse_item(self, response):
li_list = response.xpath('/html/body/div[2]/div[3]/ul[2]/li')
for li in li_list:
title = li.xpath('./span[3]/a/text()').extract_first()
detail_url =
item = TitleItem()
item['title'] = title
yield item
def parse_detail(self, response):
content = response.xpath('/html/body/div[3]/div[2]/div[2]/div[2]/pre/text()').extract_first()
item = ContentItem()
item['content'] = content
yield item
-
解決上述問題
手動發送請求決議詳情頁的內容,請求傳參,傳給一個item
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from sunPro.items import SunproItem
class SunSpider(CrawlSpider):
name = 'sun'
# allowed_domains = ['www.xxx.com']
start_urls = ['http://wz.sun0769.com/political/index/politicsNewest?id=1&page=1']
link = LinkExtractor(allow=r'id=1&page=\d+')
rules = (
Rule(link, callback='parse_item', follow=False),
)
def parse_item(self, response):
li_list = response.xpath('/html/body/div[2]/div[3]/ul[2]/li')
for li in li_list:
title = li.xpath('./span[3]/a/text()').extract_first()
detail_url = "http://wz.sun0769.com/" + li.xpath('./span[3]/a/@href').extract_first()
item = SunproItem()
item['title'] = title
yield scrapy.Request(url=detail_url, callback=self.parse_detail, meta={'item': item})
def parse_detail(self, response):
content = response.xpath('/html/body/div[3]/div[2]/div[2]/div[2]/pre/text()').extract_first()
item = response.meta['item']
item['content'] = content
yield item
分布式
實際應用中很少,一般都是面試時問道,主要是文原理,
-
概念:搭建一個分布式機群,共同執行一組代碼,聯合對同一個資源的資料進行分布且聯合爬取
-
實作方式:
簡稱:
scrapy + redis全稱:
Scrapy框架 + scrapy-redis組件 -
原生的scrapy框架無法實作分布式
原生scrapy的調度器和管道無法共享
-
scrapy-redis組件的作用
可以給原生的scrapy框架,提供可以被共享的管道和調度器
-
環境安裝
pip install scrapy-redis -
實作流程
修改爬蟲檔案中爬蟲類對應的操作
-
導包:
from scrapy_redis.spiders import RedisCrawlSpiderCrawlSpider 匯入 RedisCrawlSpider Spider 匯入 RedisSpider -
爬蟲類的父類修改成
RedisCrawlSpider -
將start_urls洗掉,添加一個redis_key='可以被共享調度器佇列的名稱'
-
進行常規的請求和決議和向管道提交item操作即可
對
settings.py進行配置-
- 配置管道
ITEM_PIPELINES = { # scrapy組件中有管道,是基于redis的,所以目前只能用redis存盤 'scrapy_redis.pipelines.RedisPipeline':400, }- 調度器的配置
# 增加了一個去重容器類的配置,作用使用Redis的set集合來存盤請求的指紋資料,從而實作請求去重的持久化 DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter" # 使用scrapy-redis組件自己的調度器 SCHEDULER = "scraoy_redis.scheduler.Scheduler" # 配置調度器是否要持久化,也就是爬蟲結束,是否清空Redis中請求對列和去重的set,true表示持久化存盤,不清空 SCHEDULER_PERSIST = True- 對Redis進行配置
REDIS_HOST = 'redis服務的ip地址' REDIS_PORT = 6379
對redis組態檔進行修改
- 56行:# bind 127.0.0.1
- 75行:protected-mode no
啟動Redis服務和客戶端
-
攜帶組態檔啟動redis,在redis安裝目錄下運行cmd
redis-server.exe redis.windows.conf -
啟動客戶端
redis-cli
啟動程式
-
在終端中進入到爬蟲檔案對應的目錄中
scrapy runspider spiderName.py -
向調度器的佇列中扔入一個起始的url
redisl-clilpush redis_key的屬性值(被共享的調度器佇列名稱) 起始的網址
-
示例代碼
# -*- coding: utf-8 -*-
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from scrapy_redis.spiders import RedisCrawlSpider
from fbsPro.items import FbsproItem
class FbsSpider(RedisCrawlSpider):
name = 'fbs'
# allowed_domains = ['www.xxx.com']
# start_urls = ['http://www.xxx.com/']
redis_key = 'sunQueue' # 可被共享的調度器佇列的名稱
rules = (
Rule(LinkExtractor(allow=r'id=1&page=\d+'), callback='parse_item', follow=True),
)
def parse_item(self, response):
li_list = response.xpath('/html/body/div[2]/div[3]/ul[2]/li')
for li in li_list:
title = li.xpath('./span[3]/a/text()').extract_first()
item = FbsproItem()
item['title'] = title
yield item
增量式
-
監測網站資料更新情況,以便于爬取到最新更新的網站
-
核心:去重
-
記錄儀:
特性:永久性存盤(redis中的set)
爬取過的資料對應的url
-
可以以明文的形式存盤(url資料長度較短)
-
記錄的資料對其生成一個
資料指紋(url資料長度比較長)資料指紋就是該組資料的唯一標識
-
示例代碼
# -*- coding: utf-8 -*-
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from redis import Redis
from zlsPro.items import ZlsproItem
class ZlsSpider(CrawlSpider):
name = 'zls'
# allowed_domains = ['www.xxx.com']
start_urls = ['http://wz.sun0769.com/political/index/politicsNewest?id=1&page=1']
# 創建redis的鏈接物件
conn = Redis(host="127.0.0.1", port=6379, password='redis的密碼,沒有就不寫')
rules = (
Rule(LinkExtractor(allow=r'id=1&page=\d+'), callback='parse_item', follow=False),
)
def parse_item(self, response):
# 決議出標題和詳情頁的url(詳情頁的url需要存盤到記錄表中)
li_list = response.xpath('/html/body/div[2]/div[3]/ul[2]/li')
for li in li_list:
title = li.xpath('./span[3]/a/text()').extract_first()
detail_url = "http://wz.sun0769.com/" + li.xpath('./span[3]/a/@href').extract_first()
item = ZlsproItem()
item['title'] = title
# 將進行請求發送的詳情頁的url去記錄表中進行查看
ex = self.conn.sadd('urls', detail_url)
if ex == 1:
print('資料已更新,可爬取')
yield scrapy.Request(url=detail_url, callback=self.parse_deatil, meta={'item': item})
else:
print('資料未更新,不可爬')
def parse_deatil(self, response):
item = response.meta['item']
content = response.xpath('/html/body/div[3]/div[2]/div[2]/div[2]/pre/text()').extract_first()
item['content'] = content
yield item
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/165069.html
標籤:Python
上一篇:Python爬蟲連載17-ItemPipeLine、中間件
下一篇:Python基礎一
