1、進程、線程、協成html
一、進程、線程、協成之間的關係
一、 線程是計算機中最小的工做單元。
二、 進程是提供資源供n個線程使用,即進程是最小的管理單元。python
三、協程是人爲控制的線程。web
四、總結:一、python中因爲有 GIL鎖的存在,因此一個進程中同一時刻只有一個線程被CPU調度,因此在計算密集型,使用多進程而在io密集型使用多線程。算法
二、使用協成能夠實現單線程下的併發,線程中cpu在遇到io操做時就會切到下一個線程中,這樣大大的影響的效率因而人爲的控制cpu在線程中的切換,當cpu在線程中遇到io會切換到該線程的下一個程序中這就是協成數據庫
二、異步非阻塞模塊cookie
一、gevent模塊,基於協程的異步非阻塞模塊。
二、Twisted,基於事件驅動(while循環檢測)異步非阻塞模塊(框架)。
三、非阻塞是指不等待 ,異步是指回調,執行某個任務完成以後,自動執行的函數。回調函數多線程
2、scarpy框架併發
一、重寫scrapy請求頭方法app
# -*- coding: utf-8 -*- import scrapy from scrapy.http import Request from scrapy.selector import HtmlXPathSelector #response= HtmlXPathSelector(response)主要是在parse函數中使用,
from urllib.parse import urlencode
from ..items import MyscrapyItem class BaiduSpider(scrapy.Spider): name = 'baidu' #爬蟲應用的名稱,經過此名稱啓動爬蟲命令 allowed_domains = ['www.amazon.cn'] # 容許爬取數據的域名 def __init__(self,keywords=None,*args,**kwargs): super(BaiduSpider,self).__init__(*args,**kwargs) self.keywords=keywords #主要是接受一些外部傳進來的參數 def start_requests(self): #重寫scrapy請求頭url必須寫這個名字的函數 url='https://www.amazon.cn/s/ref=nb_sb_noss_1?' #新url parmas={ 'field-keywords':self.keywords #主要是定義一些外部傳進來的參數 } url=url+urlencode(parmas,encoding='utf-8') #路徑拼接,造成一個完整的url路徑 yield Request(url,callback=self.parse_index) #yield返回數據給回調函數,也能夠是return+[]的方式返回數據給回調函數,yield返回的是生成器,return+[]返回的可迭代對象 # return [Request(url='www.baidu.com',callback=self.parse),] def parse_index(self, response): urls=response.xpath('//*[contains(@id,"result_"]/div/div[3]/div[1]/a/@href').extract() for url in urls: print(url) yield Request(url,callback=self.parse) #返回詳情頁的信息 next_url=response.urljoin(response.xpath('//*[@id="pagnNextLink"]/@href').extract_first()) yield Request(next_url,callback=self.parse_index) #返回下一頁的url路徑,返回的是個Request對象 def parse(self, response): price=response.xpath('//*[@id="priceblock_ourprice"]/text()').extract_first().strip() colour=response.xpath('//*[@id="variation_color_name"]/div/span/text()').extract_first().strip() item=MyscrapyItem() item['price']=price item['colour']=colour return item #返回的是個item對象
或者是這種格式,只要返回的是個item對象及格
obj = XiaoHuarItem(price=price, colour=colour, )
yield obj
二、items文件寫法框架
import scrapy class MyscrapyItem(scrapy.Item): price=scrapy.Field() colour=scrapy.Field()
三、自定義pipelines文件
from scrapy.exceptions import DropItem #主動觸發異經常使用的 class CustomPipeline(object): def __init__(self,val): self.vale = val #構造一個對象特有的方法 def process_item(self, item, spider): #用於存儲爬取的數據,能夠存儲在文件也能夠存儲在數據庫。 f=opne('item.log','a+')
f.write(item.price,item.colour)
f.close() return item # return表示會被後續的pipeline繼續處理,由於有能夠一個爬蟲項目會有多個爬蟲程序一塊兒執行,可是都有寫入同一個文件中,那就讓第一個文件寫入,其他後面的文件不用重複寫入。 # raise DropItem() # 表示將item丟棄,不會被後續pipeline處理 # if spider.name='baidu': #這就表示該文件只是存儲這個名字的爬蟲爬取的數據
#f=opne('item.log','a+')
#f.write(item.price,item.colour)
#f.close()
#return item
@classmethod def from_crawler(cls, crawler):#初始化時候,用於建立pipeline對象,crawler這個類能夠獲得settings配置文件內的數據,若是須要給pipeline對象建立某個額外的參數就能夠用這中方法 val = crawler.settings.getint('MMMM') #val = crawler.settings.get('MMMM') 讀取配置文件的某個信息 return cls(val) def open_spider(self,spider): #在爬蟲程序開始執行以前調用該方法 print('000000') def close_spider(self,spider): #在爬蟲程序關閉後調用該方法 print('111111')
總結:這五個方法的執行順序: 先執行from_crawler方法,用於初始化數據的時候讀取配置文件,而後再執行爬蟲程序的__init__方法,而後程序啓動前執行open_spider方法,程序結束前保存數據時執行process_item方法,程序結果後
執行close_spider方法。
四、自定義url去重功能
scrapy默認使用 scrapy.dupefilter.RFPDupeFilter 進行去重,settings相關配置有:
DUPEFILTER_CLASS = 'scrapy.dupefilter.RFPDupeFilter' #去重文件,能夠自定義 DUPEFILTER_DEBUG = False #默認去重功能是關閉的能夠在settings中打開去重功能也能夠在爬蟲程序中手動指定去重 yield Request(url,callback=self.parse,dont_filter=True) JOBDIR = "保存範文記錄的日誌路徑,如:/root/" # 最終路徑爲 /root/requests.seen 默認url是存放在內存中的,只要添加上jobdir就能夠吧url存放在指定文件中
本身寫一個去重規則
class RepeatUrl: def __init__(self): self.visited_url = set() #默認是一個集合,也能夠是自定義連接一個數據庫,文件等 @classmethod def from_settings(cls, settings): """ 初始化時,調用 """ return cls() def request_seen(self, request): """ 檢測當前請求是否已經被訪問過 :param request: :return: True表示已經訪問過;False表示未訪問過 """ if request.url in self.visited_url: return True self.visited_url.add(request.url) return False def open(self): """ 開始爬去請求時,調用 :return: """ print('open replication') def close(self, reason): """ 結束爬蟲爬取時,調用 :param reason: :return: """ print('close replication') def log(self, request, spider): """ 記錄日誌 :param request: :param spider: :return: """ print('repeat', request.url)
注意:寫好自定義的去重規則後要將該文件設置到settings中該規程才能生效 好比:DUPEFILTER_CLASS = 'scrapy.dupefilter.RFPDupeFilter'
五、自定義命令
#自定製一鍵啓動全部爬蟲命令
from scrapy.commands import ScrapyCommand from scrapy.utils.project import get_project_settings class Command(ScrapyCommand): requires_project = True def syntax(self): return '[options]' def short_desc(self): return 'Runs all of the spiders' def run(self, args, opts): #這是源碼的入口 spider_list = self.crawler_process.spiders.list() for name in spider_list: #循環全部的爬蟲程序名稱 self.crawler_process.crawl(name, **opts.__dict__) #準備全部的爬蟲程序 self.crawler_process.start() #開始爬取任務
注意:一、在spiders同級建立任意目錄,如:commands
二、在其中建立 crawlall.py 文件 (此處文件名就是自定義的命令)
三、在settings.py 中添加配置 COMMANDS_MODULE = '項目名稱.目錄名稱'
四、在項目目錄執行命令:scrapy crawlall
#自定製在py文件中啓動某個爬蟲命令
from scrapy.cmdline import execute
execute(['scrapy', '命令', '爬蟲程序名稱','-a','傳入的參數'])
eg:execute(['scrapy', 'crawl', 'baidu','-a','keywords=iphone7'])
注意:自定製的py文件名稱必須是entrypoint.py
六、自定義信號
from scrapy import signals class MyExtension(object): def __init__(self, value): self.value = value #定義本身想要的方法 @classmethod def from_crawler(cls, crawler): #擴展功能,在擴展功能裏註冊信號 val = crawler.settings.getint('MMMM')#讀取配置文件信息 ext = cls(val) crawler.signals.connect(ext.spider_opened, signal=signals.spider_opened) #註冊信號 crawler.signals.connect(ext.spider_closed, signal=signals.spider_closed) #註冊信號,能夠註冊不少信號,看源碼 return ext #若是不須要擴展功能那麼就直接註冊信號便可 def spider_opened(self, spider): #爬蟲程序啓動時觸發的信號 print('open') def spider_closed(self, spider): #爬蟲程序關閉時觸發的信號 print('close')
註釋:一、在項目目錄下建立一個py文件,將自定義的信號寫入該文件,建議py文件名爲extensions
二、在settings中註冊自定義信號的文件
eg:EXTENSIONS = {'scrapy.extensions.telnet.TelnetConsole': 300,}
七、中間件
#下載中間件
class DownMiddleware1(object): def process_request(self, request, spider): #請求過去的中間件帶的時request """ 請求須要被下載時,通過全部下載器中間件的process_request調用 return: None,繼續後續中間件去下載,默認就是什麼都不返回; Response對象,中止process_request的執行,開始執行process_response,先導入Response(from scrapy.http import Response)
eg:return Response(url='http://www.baidu.com',request=request) #只要返回Response對象就不會執行後續的Request,而是直接執行Response Request對象,中止中間件的執行,將Request從新調度器 #若是返回的是Request對象那就不會執行後續的Request和Reques而是直接到引擎,從新調度 raise IgnoreRequest異常,中止process_request的執行,開始執行process_exception """ pass def process_response(self, request, response, spider): #返回數據的中間件帶的時response """ spider處理完成,返回時調用,默認返回的就是Response return: Response 對象:轉交給其餘中間件process_response Request 對象:中止中間件,request會被從新調度下載 raise IgnoreRequest 異常:調用Request.errback """ return response def process_exception(self, request, exception, spider): """ 當下載處理器(download handler)或 process_request() (下載中間件)拋出異常的時候調用 :return: None:繼續交給後續中間件處理異常; Response對象:中止後續process_exception方法,正確執行後續方法 Request對象:中止中間件後續方法,request將會被從新調用下載 """ return None
註釋:一、在項目目錄下建立.py文件,將中間件代碼寫在該文件中,建議文件名稱爲DownMiddleware
2、在settings中註冊中間件,DOWNLOADER_MIDDLEWARES = {'chouti.middlewares.MyCustomDownloaderMiddleware': 600,},寫了幾個中間件就註冊幾個中間件
三、能夠在中間件的process_request中將全部的Request請求添加上請求頭,添加cookies等操做,也能夠添加代理,判斷那個Request須要代理。
#爬蟲中間件 class SpiderMiddleware(object): def process_spider_input(self,response, spider): """下載完成,執行,而後交給parse處理,默認返回none""" pass def process_spider_output(self,response, result, spider): """spider處理完成,返回時調用,return: 必須返回包含 Request 或 Item 對象的可迭代對象(iterable)""" return result
def process_spider_exception(self,response, exception, spider): """異常調用,return: None,繼續交給後續中間件處理異常;含 Response 或 Item 的可迭代對象(iterable),交給調度器或pipeline""" return None
def process_start_requests(self,start_requests, spider): """爬蟲啓動時調用,return: 包含 Request 對象的可迭代對象,因爲爬蟲只是啓動一次因此process_start_requests也只是調用一次 """ return start_requests
註釋:一、在項目目錄下建立.py文件,將中間件代碼寫在該文件中,建議文件名稱爲SpiderMiddleware
二、在settings中註冊中間件,SPIDER_MIDDLEWARES = { 'chouti.middlewares.ChoutiSpiderMiddleware': 600,},寫了幾個中間件就註冊幾個中間件
八、自定義代理
代理,須要在環境變量中設置 from scrapy.contrib.downloadermiddleware.httpproxy import HttpProxyMiddleware 方式一:使用默認
import os os.environ {
http_proxy:http://root:woshiniba@192.168.11.11:9999/ #有的代理連接是須要用戶名和密碼的,這個就是root爲用戶名,woshiniba爲密碼 https_proxy:http://192.168.11.11:9999/
} #該方法只須要在爬蟲程序文件的類的__init__中添加上就能夠了,程序會判斷只要是_prxoy結尾的就會吧這個代理拿出來
方式二:使用自定義下載中間件 def to_bytes(text, encoding=None, errors='strict'): if isinstance(text, bytes): return text if not isinstance(text, six.string_types): raise TypeError('to_bytes must receive a unicode, str or bytes ' 'object, got %s' % type(text).__name__) if encoding is None: encoding = 'utf-8' return text.encode(encoding, errors) class ProxyMiddleware(object):#代理中間件 def process_request(self, request, spider): PROXIES = [ {'ip_port': '111.11.228.75:80', 'user_pass': ''}, {'ip_port': '120.198.243.22:80', 'user_pass': ''}, {'ip_port': '111.8.60.9:8123', 'user_pass': ''}, {'ip_port': '101.71.27.120:80', 'user_pass': ''}, {'ip_port': '122.96.59.104:80', 'user_pass': ''}, {'ip_port': '122.224.249.122:8088', 'user_pass': ''}, ] proxy = random.choice(PROXIES)#隨機選擇一個代理,高明就高明在這個地方,默認代理是不能隨機生成的,全部也會有被封的風險。 if proxy['user_pass'] is not None: request.meta['proxy'] = to_bytes("http://%s" % proxy['ip_port'])#添加上代理 encoded_user_pass = base64.encodestring(to_bytes(proxy['user_pass']))#代理加密,固定加密方法 request.headers['Proxy-Authorization'] = to_bytes('Basic ' + encoded_user_pass) #代理受權 #print "**************ProxyMiddleware have pass************" + proxy['ip_port'] else: #print "**************ProxyMiddleware no pass************" + proxy['ip_port'] request.meta['proxy'] = to_bytes("http://%s" % proxy['ip_port'])#添加代理 註釋:一、在爬蟲項目路徑中建立代理文件
二、在settings中註冊上代理 DOWNLOADER_MIDDLEWARES = { 'step8_king.middlewares.ProxyMiddleware': 500,}
九、settings配置文件詳解
1. 爬蟲名稱 BOT_NAME = 'step8_king' 2. 爬蟲應用路徑 SPIDER_MODULES = ['step8_king.spiders'] #其實就是建立的爬蟲程序名稱+spiders NEWSPIDER_MODULE = 'step8_king.spiders'
3. 客戶端 user-agent請求頭 # USER_AGENT = 'step8_king (+http://www.yourdomain.com)' 4. 禁止爬蟲配置(機器人協議) # ROBOTSTXT_OBEY = True
5. 併發請求數 # CONCURRENT_REQUESTS = 4 #一個線程最大能併發多少個請求,力度粗 6. 延遲下載秒數 # DOWNLOAD_DELAY = 2 默認是秒,每多少秒一次下載 7. 單域名訪問併發數,而且延遲下次秒數也應用在每一個域名 力度細 # CONCURRENT_REQUESTS_PER_DOMAIN = 2 # 單IP訪問併發數,若是有值則忽略:CONCURRENT_REQUESTS_PER_DOMAIN,而且延遲下次秒數也應用在每一個IP 力度細 # CONCURRENT_REQUESTS_PER_IP = 3 8. 是否支持cookie,cookiejar進行操做cookie # COOKIES_ENABLED = True # COOKIES_DEBUG = True
9. Telnet用於查看當前爬蟲的信息,操做爬蟲等... # 使用telnet ip port ,而後經過命令操做 # TELNETCONSOLE_ENABLED = True # TELNETCONSOLE_HOST = '127.0.0.1' # TELNETCONSOLE_PORT = [6023,] 10. 默認請求頭,死板 # Override the default request headers: # DEFAULT_REQUEST_HEADERS = { # 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', # 'Accept-Language': 'en', # } 11. 定義pipeline處理請求 # ITEM_PIPELINES = { # 'step8_king.pipelines.JsonPipeline': 700, # 'step8_king.pipelines.FilePipeline': 500, # } 12. 自定義擴展,基於信號進行調用 # Enable or disable extensions # See http://scrapy.readthedocs.org/en/latest/topics/extensions.html # EXTENSIONS = { # # 'step8_king.extensions.MyExtension': 500, # } 13. 爬蟲容許的最大深度,能夠經過meta查看當前深度;0表示無深度 # DEPTH_LIMIT = 3 14. 爬取時,0表示深度優先Lifo(默認);1表示廣度優先FiFo # 後進先出,深度優先 # DEPTH_PRIORITY = 0 # SCHEDULER_DISK_QUEUE = 'scrapy.squeue.PickleLifoDiskQueue' # SCHEDULER_MEMORY_QUEUE = 'scrapy.squeue.LifoMemoryQueue'
# 先進先出,廣度優先 # DEPTH_PRIORITY = 1 # SCHEDULER_DISK_QUEUE = 'scrapy.squeue.PickleFifoDiskQueue' # SCHEDULER_MEMORY_QUEUE = 'scrapy.squeue.FifoMemoryQueue' 15. 調度器隊列 # SCHEDULER = 'scrapy.core.scheduler.Scheduler' # from scrapy.core.scheduler import Scheduler 16. 訪問URL去重 # DUPEFILTER_CLASS = 'step8_king.duplication.RepeatUrl' """ 17. 自動限速算法 from scrapy.contrib.throttle import AutoThrottle 自動限速設置 1. 獲取最小延遲 DOWNLOAD_DELAY 2. 獲取最大延遲 AUTOTHROTTLE_MAX_DELAY 3. 設置初始下載延遲 AUTOTHROTTLE_START_DELAY 4. 當請求下載完成後,獲取其"鏈接"時間 latency,即:請求鏈接到接受到響應頭之間的時間 5. 用於計算的... AUTOTHROTTLE_TARGET_CONCURRENCY target_delay = latency / self.target_concurrency new_delay = (slot.delay + target_delay) / 2.0 # 表示上一次的延遲時間 new_delay = max(target_delay, new_delay) new_delay = min(max(self.mindelay, new_delay), self.maxdelay) slot.delay = new_delay """
十、https證書設置
Https訪問時有兩種狀況: 1. 要爬取網站使用的可信任證書(默認支持) DOWNLOADER_HTTPCLIENTFACTORY = "scrapy.core.downloader.webclient.ScrapyHTTPClientFactory" DOWNLOADER_CLIENTCONTEXTFACTORY = "scrapy.core.downloader.contextfactory.ScrapyClientContextFactory" 2. 要爬取網站使用的自定義證書 DOWNLOADER_HTTPCLIENTFACTORY = "scrapy.core.downloader.webclient.ScrapyHTTPClientFactory" DOWNLOADER_CLIENTCONTEXTFACTORY = "step8_king.https.MySSLFactory" # https.py from scrapy.core.downloader.contextfactory import ScrapyClientContextFactory from twisted.internet.ssl import (optionsForClientTLS, CertificateOptions, PrivateCertificate) class MySSLFactory(ScrapyClientContextFactory): def getCertificateOptions(self): from OpenSSL import crypto v1 = crypto.load_privatekey(crypto.FILETYPE_PEM, open('/Users/wupeiqi/client.key.unsecure', mode='r').read()) #打開本地保存的證書文件 v2 = crypto.load_certificate(crypto.FILETYPE_PEM, open('/Users/wupeiqi/client.pem', mode='r').read()) return CertificateOptions( privateKey=v1, # pKey對象 certificate=v2, # X509對象 verify=False, method=getattr(self, 'method', getattr(self, '_ssl_method', None)) ) 其餘: 相關類 scrapy.core.downloader.handlers.http.HttpDownloadHandler scrapy.core.downloader.webclient.ScrapyHTTPClientFactory scrapy.core.downloader.contextfactory.ScrapyClientContextFactory 相關配置 DOWNLOADER_HTTPCLIENTFACTORY DOWNLOADER_CLIENTCONTEXTFACTORY