爬取網站前3_下載限速

Pop_Rain發表於2017-05-19

如果我們爬取網站的速度過快,就會面臨被封禁或是造成伺服器過載的風險。為了降低這些風險,我們可以在兩次下載之間新增延時(其實在解析robots.txt中一般就會有明確指出 下載限速delay的閾值,我們可以根據這個值進行下載限速的設定),從而對爬蟲限速。下面是實現下載限速該功能的程式碼:

#爬取網站的下載限速功能的類的實現,需要import datetime
#Throttle類記錄了每個域名上次訪問的時間,如果當前時間距離上次訪問時間小於指定延時, 則執行睡眠操作
#我們可以在每次下載之前呼叫Throttle對爬蟲進行限速。
class Throttle:  #爬取網站的下載限速功能的類的實現,每次在download下載前使用
    """Add a delay between downloads to the same domain"""
    def __init__(self, delay):
        self.delay = delay  # value of delay between downloads for each domain
        self.domains = {}   # timestamp of when a domain was last accessed記錄上次訪問的時間,小知識timestamp:時間戳是指格林威治時間1970年01月01日00時00分00秒(北京時間1970年01月01日08時00分00秒)起至現在的總秒數。

    def wait(self, url):
        domain = urllib.parse.urlparse(url).netloc
        last_accessed = self.domains.get(domain)

        if self.delay>0 and last_accessed is not None:
            sleep_secs = self.delay - (datetime.datetime.now() - last_accessed).seconds
            if sleep_secs > 0:
                time.sleep(sleep_secs)  #domain has been accessed recently,so need to sleep
        self.domains[domain] = datetime.datetime.now()

#呼叫這個下載限速類
throttle = Throttle(delay)
throttle.wait(url)
result = download(url, hearders, proxy=proxy, num_retries=num_retries)

將下載限速功能整合到之前的連結爬蟲,有如下程式碼:

import urllib.request
import urllib.error 
import re #正規表示式
import urllib.parse #將url連結從相對路徑(瀏覽器可懂但python不懂)轉為絕對路徑(python也懂了)
import urllib.robotparser #爬取資料前解析網站robots.txt檔案,避免爬取網站所禁止或限制的
import datetime  #下載限速功能所需模組
def download(url, user_agent = "brain", proxy = None, num_retries = 2):  #下載url網頁,proxy是支援代理功能,初始值為None,想要設定就直接傳引數即可
    print("downloading:",url)
    header = {"user-agent": user_agent} #設定使用者代理,而不使用python預設的使用者代理Python-urllib/3.6
    req = urllib.request.Request(url, headers = header)    
    
    opener = urllib.request.build_opener()  #為支援代理功能時刻準備著
    if proxy:   #如果設定了proxy,那麼就進行以下設定以實現支援代理功能
        proxy_params = { urllib.parse.urlparse(url).scheme: proxy }
        opener.add_handler(urllib.request.ProxyHandler(proxy_params))
        response = opener.open(req)
        
    try:
        html = urllib.request.urlopen(req).read()
    except urllib.error.URLError as e:    #下載過程中出現問題
        print("download error:",e.reason)
        html = None

        if num_retries > 0:     #錯誤4XX發生在請求存在問題,而5XX錯誤則發生在服務端存在問題,所以在發生5XX錯誤時重試下載
            if hasattr(e, "code") and 500<= e.code <600:
                return  download(url, user_agent, num_retries-1)  # recursively retry 5XX HTTP errors
    return html
#download("http://example.webscraping.com") #訪問正常
#download("http://httpstat.us/500") #這個網頁測試用,一直是5XXerror

#跟蹤連結的爬蟲
#link_crawler()函式傳入兩個引數:要爬取的網站URL、用於跟蹤連結的正規表示式。
def link_crawler(seed_url, link_regex):
    """先下載 seed_url 網頁的原始碼,然後提取出裡面所有的連結URL,接著對所有匹配到的連結URL與link_regex 進行匹配,
如果連結URL裡面有link_regex內容,就將這個連結URL放入到佇列中,
下一次 執行 while crawl_queue: 就對這個連結URL 進行同樣的操作。
反反覆覆,直到 crawl_queue 佇列為空,才退出函式。"""
    crawl_queue = [seed_url]
    seen = set(crawl_queue) #有可能連結中互相重複指向,為避免爬取相同的連結,所以我們需要記錄哪些連結已經被爬取過(放在集合seen中),若已被爬取過,不再爬取
    while crawl_queue:
        url = crawl_queue.pop()
        
        rp = urllib.robotparser.RobotFileParser()   #爬取前解析網站robots.txt,檢查是否可以爬取網站,避免爬取網站禁止或限制的
        rp.set_url("http://example.webscraping.com/robots.txt")
        rp.read()
        user_agent = "brain"
        if rp.can_fetch(user_agent, url):  #解析後發現如果可以正常爬取網站,則繼續執行
            
            #爬取網站的下載限速功能的類的呼叫,每次在download下載前使用
            throttle = Throttle(delay=5) #這裡例項網站robots.txt中的delay值為5
            throttle.wait(url)
            html = download(url)   #html = download(url, hearders, proxy=proxy, num_retries=num_retries)這裡可以傳所需要的引數
            
            html = str(html)
            #filter for links matching our regular expression
            if html == None:
                continue
            for link in get_links(html):
                if re.match(link_regex, link):
                    link = urllib.parse.urljoin(seed_url, link) #把提取的相對url路徑link(view/178)轉化成絕對路徑(/view/Poland-178)link
                    if link not in seen:  #判斷是否之前已經爬取
                        seen.add(link) #之前沒有的話加在集合中以便後續繼續判斷
                        crawl_queue.append(link) #之前沒有的話這個連結可用,放在列表中繼續進行爬取
        else:
            print("Blocked by %s robots,txt" % url)
            continue
        
def get_links(html):
    """用來獲取一個html網頁中所有的連結URL"""
    #做了一個匹配模板 webpage_regex,匹配 <a href="xxx"> or <a href='xxx'>這樣的字串,並提取出裡面xxx的URL,請注意這裡的xxxURL很可能是原始碼中相對路徑,eg view/1 正常訪問肯定是打不開的
    webpage_regex = re.compile('<a href=["\'](.*?)["\']', re.IGNORECASE)
    return re.findall(webpage_regex,html)
    #return re.findall('<a[^>]+href=["\'](.*?)["\']', html)也可以這樣實現,但沒有上面的先編譯模板再匹配好

class Throttle:  #爬取網站的下載限速功能的類的實現,每次在download下載前使用
    """Add a delay between downloads to the same domain"""
    def __init__(self, delay):
        self.delay = delay  # value of delay between downloads for each domain
        self.domains = {}   # timestamp of when a domain was last accessed記錄上次訪問的時間,小知識timestamp:時間戳是指格林威治時間1970年01月01日00時00分00秒(北京時間1970年01月01日08時00分00秒)起至現在的總秒數。

    def wait(self, url):
        domain = urllib.parse.urlparse(url).netloc
        last_accessed = self.domains.get(domain)

        if self.delay>0 and last_accessed is not None:
            sleep_secs = self.delay - (datetime.datetime.now() - last_accessed).seconds
            if sleep_secs > 0:
                time.sleep(sleep_secs)  #domain has been accessed recently,so need to sleep
        self.domains[domain] = datetime.datetime.now()

#只想找http://example.webscraping.com/index... or http://example.webscraping.com/view...
link_crawler("http://example.webscraping.com", "/(index|view)")

相關文章