scraping_編寫第一個網路爬蟲

Pop_Rain發表於2017-05-17

引言與前期準備

在訪問網頁時,有時遇到網頁訪問出錯(可能請求錯誤、可能服務端錯誤)從而為導致無法正常下載網頁,因此我們要能健壯地捕獲訪問過程的錯誤並作出相應處理。這裡轉載一篇python3中的urllib.error實現:Python3網路爬蟲(三):urllib.error異常。自己根據這篇文章及實際python3中遇到的問題成功實現了正常捕獲網頁訪問error異常。以下程式碼實現兩個功能:遇到5xx錯誤重試下載;設定使用者代理
#此程式碼實現並測試爬取下載網頁中的幾個問題或方法:遇到5xx錯誤重試下載;設定使用者代理
import urllib.request
import urllib.error
def download(url, user_agent = "brain", num_retries = 2):  #下載網頁
    print("downloading:",url)
    header = {"user-agent": user_agent} #設定使用者代理,而不使用python預設的使用者代理Python-urllib/3.6
    req = urllib.request.Request(url, headers = header)
    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

執行結果:

downloading: http://httpstat.us/500
download error: Internal Server Error
downloading: http://httpstat.us/500
download error: Internal Server Error
downloading: http://httpstat.us/500
download error: Internal Server Error


正文開始

爬取網站,我們通常需要做兩件事:下載網頁(該過程一般稱為爬取 crawling);提取資料(或儲存資料,或繼續分析資料等)

一、 下載網頁(爬取 crawling)

三種爬取網站的常見方法:爬取網站地圖、遍歷每個網頁的資料庫ID、跟蹤網頁連結

1. 爬取網站地圖sitemap中的url連結(使用正規表示式):

#def download()實現並測試爬取下載網頁中的幾個問題或方法:遇到5xx錯誤重試下載;設定使用者代理
#def crawl_sitemap(url)實現爬取一個網站sitemap中url資料(用到正規表示式),並且將每個url下載

import urllib.request
import urllib.error
import re
def download(url, user_agent = "brain", num_retries = 2):  #下載url網頁
    print("downloading:",url)
    header = {"user-agent": user_agent} #設定使用者代理,而不使用python預設的使用者代理Python-urllib/3.6
    req = urllib.request.Request(url, headers = header)
    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

def crawl_sitemap(url):  ##爬取一個網站sitemap中url,此url為網站sitemap的url
    sitemap = download(url)  # download the sitemap file
    sitemap = str(sitemap)   #把bytes型別轉成str型別,以便下面用正規表示式提取資料
    links = re.findall("<loc>(.*?)</loc>", sitemap)  # extract the sitemap links這裡用了正規表示式,返回的是結果列表
    for link in  links:
        html = download(link) #把爬取的url都下載下來

crawl_sitemap("http://example.webscraping.com/sitemap.xml")

2. ID遍歷爬蟲:

我們無法完全依靠檔案提供的url連結,當結果不符合預期(sitemap本身提供的連結有問題)或需求有變時,我們將不再依賴sitemap而轉而利用網站結構的弱點更加輕鬆地訪問所有內容。
http://example.webscraping.com/view/Brazil-3
http://example.webscraping.com/view/ Australia-2
可以看出上述url只在結尾處有區別,包括國家名和ID。一般情況下,web伺服器會忽略這個字串,只使用ID來匹配資料庫中的相關記錄。因此下面我們將其移除,載入:http://example.webscraping.com/view/3,測試發現連結可用能正常開啟。因此現在我們忽略頁面別名而直接使用ID來下載所有國家的頁面
#def download()實現並測試爬取下載網頁中的幾個問題或方法:遇到5xx錯誤重試下載;設定使用者代理
#def crawl_sitemap(url)實現爬取一個網站sitemap中url資料(用到正規表示式),並且將每個url下載

import urllib.request
import urllib.error 
import re #正規表示式
import itertools #使用ID遍歷
def download(url, user_agent = "brain", num_retries = 2):  #下載url網頁
    print("downloading:",url)
    header = {"user-agent": user_agent} #設定使用者代理,而不使用python預設的使用者代理Python-urllib/3.6
    req = urllib.request.Request(url, headers = header)
    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

""" #使用正規表示式的爬蟲
    def crawl_sitemap(url):  ##爬取一個網站sitemap中url,此url為網站sitemap的url
    sitemap = download(url)  # download the sitemap file
    sitemap = str(sitemap)   #把bytes型別轉成str型別,以便下面用正規表示式提取資料
    links = re.findall("<loc>(.*?)</loc>", sitemap)  # extract the sitemap links這裡用了正規表示式,返回的是結果列表
    for link in  links:
        html = download(link) #把爬取的url都下載下來"""

#使用ID遍歷的爬蟲
max_errors = 5
num_errors = 0
for page in itertools.count(1):  #如果ID在資料庫中不連續,則設定不連續次數max_errors達到時退出
    url = "http://example.webscraping.com/view/%d" % page
    html = download(url)
    if html is None:  #download出錯html為None,說明遇到不連續的ID了
        num_errors += 1
        if num_errors == max_errors:
            break
    else:
        num_errors = 0

crawl_sitemap("http://example.webscraping.com/sitemap.xml")

3. 跟蹤網頁連結:

ID遍歷在資料庫中多數為大數的不連續ID情況下很不奏效,當上述兩種方法效果都不理想時我們需要讓爬蟲表現得更像普通使用者,跟蹤連結訪問感興趣的內容。通過跟蹤所有連結,我們可以很容易地下載整個網站的頁面,但這種方法會下載大量我們並不需要的網頁,因此我們這裡也需要結合正規表示式來確定需要下載哪些頁面,明確範圍和縮小下載數量。

import urllib.request
import urllib.error 
import re #正規表示式
import urllib.parse #將url連結從相對路徑(瀏覽器可懂但python不懂)轉為絕對路徑(python也懂了)
def download(url, user_agent = "brain", num_retries = 2):  #下載url網頁
    print("downloading:",url)
    header = {"user-agent": user_agent} #設定使用者代理,而不使用python預設的使用者代理Python-urllib/3.6
    req = urllib.request.Request(url, headers = header)
    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
"""
#使用正規表示式的爬蟲
    def crawl_sitemap(url):  ##爬取一個網站sitemap中url,此url為網站sitemap的url
    sitemap = download(url)  # download the sitemap file
    sitemap = str(sitemap)   #把bytes型別轉成str型別,以便下面用正規表示式提取資料
    links = re.findall("<loc>(.*?)</loc>", sitemap)  # extract the sitemap links這裡用了正規表示式,返回的是結果列表
    for link in  links:
        html = download(link) #把爬取的url都下載下來


#使用ID遍歷的爬蟲
max_errors = 5
num_errors = 0
for page in itertools.count(1):  #如果ID在資料庫中不連續,則設定不連續次數max_errors達到時退出
    url = "http://example.webscraping.com/view/%d" % page
    html = download(url)
    if html is None:  #download出錯html為None,說明遇到不連續的ID了
        num_errors += 1
        if num_errors == max_errors:
            break
    else:
        num_errors = 0
"""


#跟蹤連結的爬蟲
#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()
        html = download(url)
        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) #之前沒有的話這個連結可用,放在列表中繼續進行爬取
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)也可以這樣實現,但沒有上面的先編譯模板再匹配好                                                    


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



以上程式碼的詳解可以看這篇文章,特別詳細:Python 網路爬蟲 009 (程式設計) 通過正規表示式來獲取一個網頁中的所有的URL連結,並下載這些URL連結的原始碼。以下是他對本文爬蟲的總結:

總結: 
這樣,我們就已經介紹了3種爬取一個站點或者一個網頁裡面所有的連結URL的原始碼。這些只是初步的程式,接下來,我們還可能會遇到這樣的問題: 
1 . 如果一些網站設定了 禁止爬取的URL,我們為了執行這個站點的規則,就要按照它的 robots.txt 檔案來設計爬取程式。 
2 . 在國內是上不了google的,那麼如果我們想要使用代理的方式上谷歌,就需要給我們的爬蟲程式設定代理。 
3 . 如果我們的爬蟲程式爬取網站的速度太快,可能就會被目標站點的伺服器封殺,所以我們需要限制下載速度。 (這個爬蟲就遇到了,今後一定注意)
4 . 有一些網頁裡面有類似日曆的東西,這個東西里面的每一個日期都是一個URL連結,我們有不會去爬取這種沒有意義的東西。日期是無止境的,所以對於我們的爬蟲程式來說,這就是一個爬蟲陷阱,我們需要避免陷入爬蟲陷阱。

我們需要解決上這4個問題。才能得到最終版本的爬蟲程式。












相關文章