簡體   English   中英

構建這個扭曲的網絡抓取工具時我做錯了什么?

[英]What am I doing wrong when building this twisted web scraper?

你能以我扭曲的方式告訴我錯誤嗎? 很長一段時間以來,我一直在努力使用 twisted 構建一個快速的網絡抓取工具。 使用 Queue 構建傳統的線程刮板是小菜一碟,而且到目前為止,速度更快。 仍然,我想比較扭曲,網絡爬蟲的目標是遞歸地從畫廊中找到圖像()鏈接。 並連接到這些圖像鏈接以抓取圖像 () 和/或收集更多圖像鏈接以供稍后解析。 代碼如下所示。 大多數函數都傳遞一個字典,因此我可以更概念地打包來自每個鏈接的所有信息,我嘗試線程化否則會阻塞的代碼(parsePage 函數)並使用“異步代碼”(或者我相信)來檢索 html 頁面, 標題信息。 和圖像。

到目前為止,我的主要問題是從我的 getLinkHTML 或 getImgHeader errback 中追蹤到大量“用戶超時導致連接失敗”。 我曾嘗試限制我使用信號量建立的連接數量,甚至導致我的一些代碼休眠無濟於事,以為我正在淹沒連接。 我還認為這個問題可能是由 reactor.connectTCP 引起的,因為在運行scraper大約 30 秒后會產生超時錯誤,而 connectTCP 有 30 秒的超時。 但是,我將 twisted 模塊的 connectTCP 代碼修改為 60s,運行后約 30 秒仍出現超時錯誤。 當然,用我的傳統螺紋刮刀相同的網站效果很好,而且速度快得多。

那我做錯了什么? 另外,由於我是自學成才的,所以請隨時對我的代碼提出一般性的批評,並且我在整個代碼中也有一些隨機問題。 非常感謝任何建議!

from twisted.internet import defer
from twisted.internet import reactor
from twisted.web import client
from lxml import html
from StringIO import StringIO
from os import path
import re

start_url = "http://www.thesupermodelsgallery.com/"
directory = "/home/z0e/Pictures/Pix/Twisted"
min_img_size = 100000

#maximum <a> links to get from main gallery
max_gallery_links = 500

#maximum <a> links to get from subsequent gallery/pages
max_picture_links = 35

def parsePage(info):
         
    def linkFilter(link):
    #filter unwanted <a> links
    if link is not None:
        trade_match = re.search(r'&trade=', link)
        href_split = link.split('=')
        for i in range(len(href_split)):
            if 'www' in href_split[i] and i > 0:
                link = href_split[i]
        end_pattern = r'\.(com|com/|net|net/|pro|pro/)$'
        end_match = re.search(end_pattern, link)
        p_pattern = r'(.*)&p'
        p_match = re.search(p_pattern, link)
        if end_match or trade_match:
            return None
        elif p_match:
            link = p_match.group(1)
            return link
        else:
            return link
    else:
        return None
        
    # better to handle a link with 'None' value through TypeError
    # exception or through if else statements?  Compare linkFilter
    # vs. imgFilter functions
        
    def imgFilter(link):
    #filter <img> links to retain only .jpg
    try:
        jpg_match = re.search(r'.jpg', link)
        if jpg_match is not None:
            return link
        else:
            return None
    except TypeError:
        return None
        
    link_num = 0
    gallery_flag = None
    info['level'] += 1
    if info['page'] is '':
    return None
    # use lxml to parse and get document root
    tree = html.parse(StringIO(info['page']))
    root = tree.getroot()
    root.make_links_absolute(info['url'])
    # info['level'] = 1 corresponds to first recursive layer (i.e. main gallery page)
    # info['level'] > 1 will be all other <a> links from main gallery page
    if info['level'] == 1:
    link_cap = max_gallery_links
    gallery_flag = True
    else:
    link_cap = max_picture_links
    gallery_flag = False
    if info['level'] > 4:
    return None
    else:
    
    # get <img> links if page is not main gallery ('gallery_flag = False')
    # put <img> links back into main event loop to extract header information
    # to judge pictures by picture size (i.e. content-length)
    if not gallery_flag:
        for elem in root.iter('img'):
            # create copy of info so that dictionary no longer points to 
            # previous dictionary, but new dictionary for each link
            info = info.copy()
            info['url'] = imgFilter(elem.get('src'))
            if info['url'] is not None:
                reactor.callFromThread(getImgHeader, info) 
                
    # get <a> link and put work back into main event loop (i.e. w/ 
    # reactor.callFromThread...) to getPage and then parse, continuing the
    # cycle of linking        
    for elem in root.iter('a'):
        if link_num > link_cap:
            break
        else:
            img = elem.find('img')
            if img is not None:
                link_num += 1
                info = info.copy()
                info['url'] = linkFilter(elem.get('href'))
                if info['url'] is not None:
                    reactor.callFromThread(getLinkHTML, info)
                    
def getLinkHTML(info):
    # get html from <a> link and then send page to be parsed in a thread
    d = client.getPage(info['url'])
    d.addCallback(parseThread, info)
    d.addErrback(failure, "getLink Failure: " + info['url'])
    
def parseThread(page, info):
    print 'parsethread:', info['url']
    info['page'] = page
    reactor.callInThread(parsePage, info)

def getImgHeader(info):
    # get <img> header information to filter images by image size
    agent = client.Agent(reactor)
    d = agent.request('HEAD', info['url'], None, None)
    d.addCallback(getImg, info)
    d.addErrback(failure, "getImgHeader Failure: " + info['url'])

def getImg(img_header, info):
    # download image only if image is above a certain threshold size
    img_size = img_header.headers.getRawHeaders('Content-Length')  
    if int(img_size[0]) > min_img_size and img_size is not None:
    img_name = ''.join(map(urlToName, info['url']))
    client.downloadPage(info['url'], path.join(directory, img_name))
    else:
    img_header, link = None, None #Does this help garbage collecting?
    
def urlToName(char):
    #convert all unwanted characters to '-' from url and use as file name
    if char in '/\?|<>"':
    return '-'
    else:
    return char
    
def failure(error, url):
    print error
    print url

def main():
    info = dict()
    info['url'] = start_url
    info['level'] = 0
    
    reactor.callWhenRunning(getLinkHTML, info)    
    reactor.suggestThreadPoolSize(2)
    reactor.run()
    
if __name__ == "__main__":
    main()

首先,考慮根本不編寫這段代碼。 看看scrapy作為滿足您需求的解決方案。 人們已經努力讓它表現良好,如果它確實需要改進,那么當你改進它時,社區中的每個人都會受益。

接下來,不幸的是,你的代碼清單中的縮進被弄亂了,讓人很難真正看到你的代碼在做什么。 希望以下是有道理的,但您應該嘗試更正代碼清單,以便它准確反映您正在做的事情,並確保在以后的問題中仔細檢查代碼清單。

就您的代碼正在做的阻止它變快的事情而言,這里有一些想法。

程序中未完成的 HTTP 請求數沒有限制。 在不知道您實際解析的是什么 HTML 的情況下,我不知道這是否真的是個問題,但如果您最終一次發出超過 20 或 30 個 HTTP 請求,很可能會使您的網絡超載。 對於 TCP,這通常意味着連接設置不會成功(某些設置數據包會丟失並且重試次數有限制)。 由於您提到了很多連接超時錯誤,我懷疑這正在發生。

考慮程序的線程版本一次將發出多少 HTTP 請求。 Twisted 版本可能會發行更多嗎? 如果是這樣,請嘗試對此施加限制。 twisted.internet.defer.DeferredSemaphore之類的東西可能是施加此限制的一種簡單方法(盡管它遠非最佳方法,因此如果它有幫助,那么您可能想開始尋找更好的方法來施加此限制 - 但如果限制沒有幫助那么沒有必要在更好的限制機制上投入大量精力)。

接下來,通過將反應器線程池限制為最多 2 個線程,您將嚴重阻礙解析名稱的能力。 默認情況下,名稱解析(即 DNS)是使用反應器線程池完成的。 你在這里有幾個選擇。 我假設您想將解析限制為兩個並發線程是有充分理由的。

首先,您可以不理會反應器線程池,而是創建您自己的線程池來進行解析。 請參閱twisted.python.threads.ThreadPool 您可以將另一個線程池的最大值設置為 2 以獲得所需的解析行為,並且反應器可以自由使用任意數量的線程來進行名稱解析。

其次,您可以繼續降低反應堆線程池的大小,並將反應堆配置為不使用線程進行名稱解析。 twisted.names.client.createResolver會給你一個名稱解析器,它就是這樣做的,而reactor.installResolver讓你告訴反應器使用它而不是它的默認值。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM