注意:每次使用scrapy框架時,將settings中的 ROBOTSTXT_OBEY(是否遵循scrapy協議) 值改成False
css
Scrapy 是基於twisted框架開發而來,twisted是一個流行的事件驅動的python網絡框架。所以Scrapy使用了一種非阻塞(又名異步)的代碼來實現併發。html
執行流程:node
1.引擎從spider獲取初始爬行請求。 2.引擎在調度程序中調度請求,並請求下一個要爬行的請求。 3.調度程序將下一個請求返回到引擎。 4.引擎將請求發送到下載器,並經過下載器中間件(請參見process_request())。 5.一旦頁面完成下載,下載器將生成響應(使用該頁面),並將其發送到引擎,並經過下載器中間軟件(請參見process_response())。 6.引擎接收下載器的響應並將其發送給spider進行處理,並經過spider中間件進行處理(請參見process_spider_input())。 7.spider處理響應,並經過spider中間件(請參見process_spider_output())向引擎返回刮掉的項目和新請求(後續)。 8.引擎將已處理的項目發送到項目管道,而後將已處理的請求發送到計劃程序,並請求可能的下一個請求進行爬網。 9.該過程重複(從步驟1開始),直到調度程序再也不發出請求。python
1. 引擎(EGINE) 引擎負責控制系統全部組件之間的數據流,並在某些動做發生時觸發事件。有關詳細信息,請參見上面的數據流部分。 2. 調度器(SCHEDULER) 用來接受引擎發過來的請求, 壓入隊列中, 並在引擎再次請求的時候返回. 能夠想像成一個URL的優先級隊列, 由它來決定下一個要抓取的網址是什麼, 同時去除重複的網址 3. 下載器(DOWLOADER) 用於下載網頁內容, 並將網頁內容返回給EGINE,下載器是創建在twisted這個高效的異步模型上的 4. 爬蟲(SPIDERS) SPIDERS是開發人員自定義的類,用來解析responses,而且提取items,或者發送新的請求 5. 項目管道(ITEM PIPLINES) 在items被提取後負責處理它們,主要包括清理、驗證、持久化(好比存到數據庫)等操做 6. 下載器中間件(Downloader Middlewares) 位於Scrapy引擎和下載器之間,主要用來處理從EGINE傳到DOWLOADER的請求request,已經從DOWNLOADER傳到EGINE的響應response 7. 爬蟲中間件(Spider Middlewares) 位於EGINE和SPIDERS之間,主要工做是處理SPIDERS的輸入(即responses)和輸出(即requests)
#Windows平臺 1.pip3 install twisted 2.pip3 install scrapy 上述方法失敗,則直接用下面方法安裝 一、pip3 install wheel #安裝後,便支持經過wheel文件安裝軟件,wheel文件官網:https://www.lfd.uci.edu/~gohlke/pythonlibs 3、pip3 install lxml 4、pip3 install pyOpenSSL 五、下載並安裝pywin32:https://sourceforge.net/projects/pywin32/files/pywin32/ 六、下載twisted的wheel文件:http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted 七、執行pip3 install 下載目錄\Twisted-19.2.0-cp36-cp36m-win_amd64.whl 8、pip3 install scrapy #Linux平臺 一、pip3 install scrapy
#1 查看幫助 scrapy -h scrapy <command> -h #2 有兩種命令:其中Project-only必須切到項目文件夾下才能執行,而Global的命令則不須要 Global commands: startproject #建立項目(在指定文件夾下建立項目) scrapy startproject amazon genspider #建立爬蟲程序 指定名稱 限制爬取的網址(在項目目錄下) scrapy genspider amzon www.amzon.cn settings #在項目目錄下,獲得的是該項目的配置 scrapy settings --get BOT_NAME runspider #運行一個獨立的python文件,沒必要建立項目(在爬蟲程序所在的目錄下) scrapy runspider amzon.py #或者直接指定py所造的指定位置 scrapy runspider E:\python-li\爬蟲\my_scrapys\jd\jd\spiders\JD.py shell #scrapy shell url地址 在交互式調試,如選擇器規則正確與否 scrapy shell www.taobao.com fetch #單純地爬取一個頁面,不打開瀏覽器,能夠拿到請求頭 scrapy fetch --nolog http://www.baidu.com 不輸出日誌 scrapy fetch --nolog --header http://www.baidu.com 不輸出日誌 只查看頭信息 view #下載完畢後直接彈出瀏覽器,以此能夠分辨出哪些數據是ajax請求 scrapy view http://www.baidu.com version #scrapy version 查看scrapy的版本,scrapy version -v查看scrapy依賴庫的版本 scrapy version -v Project-only commands: #必須先切換到對應的項目目錄才能執行 crawl #運行爬蟲,必須建立項目才行,確保配置文件中ROBOTSTXT_OBEY = False scrapy crawl amzon check #檢測項目中有無語法錯誤 scrapy check list #列出項目中所包含的爬蟲名 scrapy list edit #編輯器,通常不用 parse #scrapy parse url地址 --callback 回調函數 #以此能夠驗證咱們的回調函數是否正確 bench #scrapy bench壓力測試 #3 官網連接 https://docs.scrapy.org/en/latest/topics/commands.html
project_name/ scrapy.cfg project_name/ __init__.py items.py pipelines.py settings.py spiders/ __init__.py 爬蟲1.py 爬蟲2.py 爬蟲3.py 文件說明: scrapy.cfg 項目的主配置信息,用來部署scrapy時使用,爬蟲相關的配置信息在settings.py文件中。 items.py 設置數據存儲模板,用於結構化數據,如:Django的Model pipelines 數據處理行爲,如:通常結構化的數據持久化 settings.py 配置文件,如:遞歸的層數、併發數,延遲下載等。強調:配置文件的選項必須大寫不然視爲無效,正確寫法USER_AGENT='xxxx' spiders 爬蟲目錄,如:建立文件,編寫爬蟲規則 注意:通常建立爬蟲文件時,以網站域名命名 import sys,os sys.stdout=io.TextIOWrapper(sys.stdout.buffer,encoding='gb18030')
在項目目錄下新建:entrypoint.py from scrapy.cmdline import execute execute(['scrapy', 'crawl', 'amzon'])
git
二、換句話說,Spiders是你爲了一個特定的網址或一組網址自定義爬取和解析頁面行爲的地方github
#一、生成初始的Requests來爬取第一個URLS,而且標識一個回調函數 start_requests() 開始請求的函數 若是存在該函數則不會請求start_urls中的地址 ? 第一個請求定義在start_requests()方法內默認從start_urls列表中得到url地址來生成Request請求,默認的回調函數是parse方法。回調函數在下載完成返回response時自動觸發 #與請求相關的全部參數: """ request全部初始化屬性 (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) callback 請求成功的回調函數 若是不指定那就是parser errback 請求失敗的回調函數 priority 指定請求優先級 調度器會讀取優先級 值越小優先級越高 dont_filter false 則要過濾 flags 給請求取別名 meta 能夠給request對象添加額外屬性 """ #二、在回調函數中,解析response而且返回值 # response 提供了兩個函數用於獲取元素 xpath css # 返回值爲列表 裏面裝的是Selector對象 # Selector對象也具有xpath 和 css 因此能夠遞歸的查找 # extract 函數用於將查找到的元素轉換爲字符串 # extract_first 獲取第一個元素的字符串內容 返回值能夠4種: 包含解析數據的字典 Item對象(交給piplines處理) ***** 新的Request對象(新的Requests也須要指定一個回調函數) ***** #scrapy.Request(list_url,callback=self.parser_list) 可迭代對象(包含Items或Request) #三、在回調函數中解析頁面內容 一般使用Scrapy自帶的Selectors如xpth,css等,但很明顯你也可使用Beutifulsoup,lxml或其餘你愛用啥用啥。 #四、最後,針對返回的Items對象將會被持久化到數據庫 經過Item Pipeline組件存到數據庫:https://docs.scrapy.org/en/latest/topics/item-pipeline.html#topics-item-pipeline) 或者導出到不一樣的文件(經過Feed exports:https://docs.scrapy.org/en/latest/topics/feed-exports.html#topics-feed-exports)
Spider 基礎的爬蟲 也是咱們呢最經常使用的爬蟲 不會對response 進行任何解析 直接傳給回調函數
SitemapSpider 站點信息爬蟲 對於須要seo優化的網站一般會在網站根目錄下建立 站點地圖文件
其中列出網站全部連接地址,而後將該文件提交給搜索引擎,搜索引擎收錄後,會查看其中的信息,例如最後跟新時間等,以便於搜索引擎的爬蟲更有效的爬取你的網頁
CrawlSpider
CrawlSpider類定義了一些規則(rule)來提供跟進link的機制,從爬取的網頁中獲取link並繼續爬取。
固定格式爬蟲
CSVFeedSpider 回調函數parse_row包含一個row用於直接提取一行內容
XMLFeedSpider,回調函數parse_node包含一個node表示一個節點
這是最簡單的spider類,任何其餘的spider類都須要繼承它(包含你本身定義的)。web
ajax
class AmazonSpider(scrapy.Spider): name = 'amazon' allowed_domains = ['www.amazon.cn'] start_urls = ['http://www.amazon.cn/'] custom_settings = { 'BOT_NAME' : 'Egon_Spider_Amazon', 'REQUEST_HEADERS' : { 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'Accept-Language': 'en', } } def parse(self, response): pass
#一、name = 'amazon' 定義爬蟲名,scrapy會根據該值定位爬蟲程序 因此它必需要有且必須惟一(In Python 2 this must be ASCII only.) #二、allowed_domains = ['www.amazon.cn'] 定義容許爬取的域名,若是OffsiteMiddleware啓動(默認就啓動), 那麼不屬於該列表的域名及其子域名都不容許爬取 若是爬取的網址爲:https://www.example.com/1.html,那就添加'example.com'到列表. #三、start_urls = ['http://www.amazon.cn/'] 若是沒有指定start_requests,就從該列表中讀取url來生成第一個請求 #四、custom_settings 值爲一個字典,定義一些配置信息,在運行爬蟲程序時,這些配置會覆蓋項目級別的配置 因此custom_settings必須被定義成一個類屬性 #五、settings 經過self.settings['配置項的名字']能夠訪問settings.py中的配置,若是本身定義了custom_settings仍是以本身的爲準 #六、logger 日誌名默認爲spider的名字 self.logger.debug('=============>%s' %self.settings['BOT_NAME']) #七、start_requests() 該方法用來發起第一個Requests請求,且必須返回一個可迭代的對象。它在爬蟲程序打開時就被Scrapy調用,Scrapy只調用它一次。 默認從start_urls裏取出每一個url來生成Request(url, dont_filter=True) #八、parse(response) 這是默認的回調函數,全部的回調函數必須返回an iterable of Request and/or dicts or Item objects. #九、closed(reason) 爬蟲程序結束時自動觸發
sql
去重規則應該多個爬蟲共享的,但凡一個爬蟲爬取了,其餘都不要爬了,實現方式以下 #方法一: 1、新增類屬性 visited=set() #類屬性 2、回調函數parse方法內: def parse(self, response): if response.url in self.visited: return None ....... self.visited.add(response.url) #方法一改進:針對url可能過長,因此咱們存放url的hash值 def parse(self, response): url=md5(response.request.url) if url in self.visited: return None ....... self.visited.add(url) #方法二:Scrapy自帶去重功能 配置文件(settings.py): DUPEFILTER_CLASS = 'scrapy.dupefilters.RFPDupeFilter' #默認的去重規則幫咱們去重,去重規則在內存中 DUPEFILTER_DEBUG = False # 是否記錄全部重複請求 默認爲第一個重複請求 JOBDIR = "保存範文記錄的日誌路徑" 在程序目錄下建一個叫seens的文件夾,將改文件的地址賦值給JOBDIR scrapy自帶去重規則默認爲RFPDupeFilter,只須要咱們指定 Request(...,dont_filter=False) ,若是dont_filter=True則告訴Scrapy這個URL不參與去重。 #方法三: 咱們也能夠仿照RFPDupeFilter自定義去重規則, from scrapy.dupefilter import RFPDupeFilter,看源碼,仿照BaseDupeFilter #步驟一:在項目目錄下建立自定義去重文件dup.py import json from scrapy.dupefilters import RFPDupeFilter class MyFilter(RFPDupeFilter): def request_seen(self, request): # 在自定義的seens文件夾下建一個myseens.txt文件,往裏面放一個空列表,使用json讀取改文件,若是路徑存在返回True 不然返回false with open(r"D:\jerry\spiderDay6\scrapys\jd\jd\seens\myseens","rt") as f: ls = json.load(f) if request.url in ls: return True else: # 添加該地址到已訪問的列表中 ls.append(request.url) with open(r"D:\jerry\spiderDay6\scrapys\jd\jd\seens\myseens", "wt") as f: json.dump(ls,f) return False #步驟二:配置文件settings.py: DUPEFILTER_CLASS = '項目名.dup.UrlFilter' # 源碼分析: from scrapy.core.scheduler import Scheduler 見Scheduler下的enqueue_request方法:self.df.request_seen(request)
response經常使用屬性與方法
text 獲取文本
body 獲取二進制
css() css選擇器
xpath() xptah解析
css與xpath返回值都是selector類型
selector經常使用方法
extract 提取字符串形式數據
extract_first 提取第一個
css() 在當前文檔上繼續查找其餘元素 返回selector類型
xpath()在當前文檔上繼續查找其餘元素 返回selector類型
shell
import scrapy class PicItem(scrapy.Item): # define the fields for your item here like: # name = scrapy.Field() title = scrapy.Field() url = scrapy.Field()
在數據解析的函數中引用PicItem()返回item
# 初始化itme對象 並返回 item = PicItem() item['title'] = title #注意賦值的方式 item['url'] = url yield item
class MyPipeline(): def process_item(self, item, spider): """ 負責將一個Item進行持久化 返回值將繼續傳遞給下一個pipeline(若是有),即使是None """ return item def open_spider(self, spider): """ 爬蟲程序啓動時執行該函數 用於初始化操做例如鏈接數據庫 """ pass def close_spider(self, spider): """ 爬蟲程序關閉時執行該函數 用於初清理操做例如關閉數據庫 """ pass # 若是提供了from_crawler這個函數, scrapy會自動執行該函數來建立pipelines # 並傳入spider對象 該對象包含了一個settings屬性 用它能夠訪問配置文件 @classmethod def from_crawler(cls, crawler): #獲得配置文件中的數據,類中的其餘函數能夠經過self.MONGO_USER獲得該配置 cls.MONGO_USER = crawler.settings["MONGO_USER"] return cls()
#數字表示優先級,數值越小優先級越高 ITEM_PIPELINES = { "name.pipelines.MysqlPipeline":10, "name.pipelines.JsonPipeline":20 }
class JsonPipeline(): def __init__(self): self.json_data = [] # 在這裏處理item的持久化 def process_item(self, item, spider): self.json_data.append(dict(item)) return item def open_spider(self, spider): pass def close_spider(self, spider): with open("pics.json","wt") as f: json.dump(self.json_data,f)
#導入 DropItem類 from scrapy.exceptions import DropItem # 在process_item中拋出異常 def process_item(self, item, spider): raise DropItem
下載器主要負責從網絡上下載數據,下載器中間件用於對請求與響應進行處理
class DownMiddleware1(object): #將要發起一個請求時執行 def process_request(self, request, spider): """ 該方法在下載器發起請求前執行 :param request: :param spider: :return: None,繼續後續中間件去下載; Response對象,中止process_request的執行,開始執行process_response Request對象,中止中間件的執行,將Request從新調度器 raise IgnoreRequest異常,中止process_request的執行,開始執行process_exception """ return None #在響應發送給引擎前執行該函數 def process_response(self, request, response, spider): """ 數據下載完成,返回spider前執行 :param response: :param result: :param spider: :return: Response 對象:轉交給其餘中間件process_response Request 對象:中止中間件,request會被從新調度下載 raise IgnoreRequest 異常:調用Request.errback """ return response #當下載過程當中或是 process_request函數執行時出現異常 將調用該函數 def process_exception(self, request, exception, spider): """ 當下載處理器(download handler)或 process_request() (下載中間件)拋出異常時執行 :param response: :param exception: :param spider: :return: None:繼續交給後續中間件處理異常; Response對象:中止後續process_exception方法 Request對象:中止中間件,request將會被從新調用下載 """ pass
DOWNLOADER_MIDDLEWARES = { 'name.middlewares.TDownloaderMiddleware': 100, } 須要注意的是優先級必定要比系統的高,由於scrapy本身有個proxy中間件
首先須要明確代理池的原理,從網頁上爬取免費的代理地址數據,在請求時,若是對方服務器,限制訪問IP,就從代理池中獲取代理地址從新訪問
網絡上有不少線程的開源代理池,這裏以IPProxyPool 爲例
下載地址:https://github.com/qiyeboy/IPProxyPool
1.下載代理池
將解壓獲得的文件夾放到咱們的python代碼目錄下
2.安裝依賴
在代代理池目錄中找到requirements.txt 複製其路徑 執行如下命令
pip3 install -r 文件路徑
這樣就能夠一次安裝大量的依賴包
3.下載webpy https://codeload.github.com/webpy/webpy/zip/py3(webpy要手動安裝)
安裝webpy
切換目錄到webpy文件夾執行如下命令
python setup.py install
4.運行IPProxy.py文件開始爬取ip
執行過程可能報錯,根據錯誤信息安裝對應的模塊便可
打開獲得的連接 https://127.0.0.1:8000/
5.在項目中請求代理池的接口獲取代理ip
在項目中創建一個py文件專門獲取ip地址 如:proxy_tool.py import requests,random ips = None def get_proxy(): global ips if not ips: ips = requests.get("http://127.0.0.1:8000/").json() a = random.choice(ips) return "http://"+a[0]+":"+str(a[1]) def delete_ip(ip): ip = ip.strip("http://").split(":")[0] res = requests.get("http://127.0.0.1:8000/delete?ip="+ip).json() print(res)
class DownMiddleware1(object): #將要發起一個請求時執行 def process_request(self, request, spider): request['user-agent'] = "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36" ip = get_proxy() #請求一秒超時,而後執行process_exception request.meta["download_timeout"]=1 request.meta["proxy"] = ip return None #在響應發送給引擎前執行該函數 def process_response(self, request, response, spider): return response #當下載過程當中或是 process_request函數執行時出現異常 將調用該函數 def process_exception(self, request, exception, spider): #刪除有問題的代理ip delete_ip(request.meta["proxy"]) #該請求沒有成功,從新發起請求 return request