【问题标题】:Multi threaded web scraper using urlretrieve on a cookie-enabled site在启用 cookie 的网站上使用 urlretrieve 的多线程网络爬虫
【发布时间】:2013-11-01 10:44:44
【问题描述】:

我正在尝试编写我的第一个 Python 脚本,并且通过大量的谷歌搜索,我认为我即将完成。不过,我需要一些帮助才能越过终点线。

我需要编写一个脚本,登录到启用 cookie 的站点,抓取一堆链接,然后生成一些进程来下载文件。我的程序在单线程中运行,所以我知道代码可以工作。但是,当我尝试创建一个下载工作者池时,我碰上了一堵墙。

#manager.py
import Fetch # the module name where worker lives
from multiprocessing import pool

def FetchReports(links,Username,Password,VendorID):
    pool = multiprocessing.Pool(processes=4, initializer=Fetch._ProcessStart, initargs=(SiteBase,DataPath,Username,Password,VendorID,))
    pool.map(Fetch.DownloadJob,links)
    pool.close()
    pool.join()


#worker.py
import mechanize
import atexit

def _ProcessStart(_SiteBase,_DataPath,User,Password,VendorID):
    Login(User,Password)

    global SiteBase
    SiteBase = _SiteBase

    global DataPath
    DataPath = _DataPath

    atexit.register(Logout)

def DownloadJob(link):
    mechanize.urlretrieve(mechanize.urljoin(SiteBase, link),filename=DataPath+'\\'+filename,data=data)
    return True

在此修订版中,代码失败是因为 cookie 尚未传输给工作人员以供 urlretrieve 使用。没问题,我可以使用 mechanize 的 .cookiejar 类将 cookie 保存在 manager 中,并将它们传递给 worker。

#worker.py
import mechanize
import atexit

from multiprocessing import current_process

def _ProcessStart(_SiteBase,_DataPath,User,Password,VendorID):
    global cookies
    cookies = mechanize.LWPCookieJar()

    opener = mechanize.build_opener(mechanize.HTTPCookieProcessor(cookies))

    Login(User,Password,opener)  # note I pass the opener to Login so it can catch the cookies.

    global SiteBase
    SiteBase = _SiteBase

    global DataPath
    DataPath = _DataPath

    cookies.save(DataPath+'\\'+current_process().name+'cookies.txt',True,True)

    atexit.register(Logout)

def DownloadJob(link):
    cj = mechanize.LWPCookieJar()
    cj.revert(filename=DataPath+'\\'+current_process().name+'cookies.txt', ignore_discard=True, ignore_expires=True)
    opener = mechanize.build_opener(mechanize.HTTPCookieProcessor(cj))

    file = open(DataPath+'\\'+filename, "wb")
    file.write(opener.open(mechanize.urljoin(SiteBase, link)).read())
    file.close

但是,这失败了,因为 opener(我认为)想要将二进制文件移回管理器进行处理,并且我收到“无法腌制对象”错误消息,指的是它试图读取文件的网页.

显而易见的解决方案是在发出 urlretrieve 请求时从 cookie jar 中读取 cookie 并手动将它们添加到标头中,但我试图避免这种情况,这就是我寻求建议的原因。

【问题讨论】:

    标签: python cookies urllib2 multiprocessing urllib


    【解决方案1】:

    以正确的方式创建多线程网络抓取工具很难。我相信你可以处理它,但为什么不使用已经完成的东西呢?

    我真的建议你去看看 Scrapy http://scrapy.org/

    这是一个非常灵活的开源网络爬虫框架,可以处理您在这里需要的大部分内容。使用 Scrapy,运行并发蜘蛛是一个配置问题,而不是编程问题 (http://doc.scrapy.org/topics/settings.html#concurrent-requests-per-spider)。您还将获得对 cookie、代理、HTTP 身份验证等的支持。

    对我来说,用 Scrapy 重写我的爬虫大约需要 4 个小时。所以请问问自己:你真的想自己解决线程问题,还是爬到别人的肩膀上,专注于网页抓取问题,而不是线程问题?

    PS。你现在用机械化吗?请注意 mechanize FAQ http://wwwsearch.sourceforge.net/mechanize/faq.html:

    “它是线程安全的吗?

    没有。据我所知,您可以在线程代码中使用 mechanize,但它不提供同步:您必须自己提供。”

    如果您真的想继续使用 mechanize,请开始阅读有关如何提供同步的文档。 (例如http://effbot.org/zone/thread-synchronization.htmhttp://effbot.org/pyfaq/what-kinds-of-global-value-mutation-are-thread-safe.htm

    【讨论】:

    • 从 OPs 的问题来看,听起来他这样做是为了教育。因此,Scrapy 无法满足他的需求。
    • 哦,对了,我没选那首曲子。但是,是的,我仍然保留我的答案,以防其他人通过谷歌得到这个答案。
    • Scrapy 看起来是一个很好的资源,随着我们需求的增长,我一定会检查的。但是,我的抓取代码已经是功能性的(并且是单线程的),并且不需要我从另一个解决方案开始的大量时间或逻辑需求。另一方面,下载更为重要,因为需要每周下载 400 多个 Excel 电子表格。
    • 我一直在你现在的位置:) 在 Scrapy 之前我使用了很多其他的抓取机制,因为我只是没有时间投入学习它。在我这样做之后,就没有回头路了——Scrapy 在使用 Python 进行网络抓取的所有方面都非常出色。老实说,我真的建议您尝试一下!
    • 我一定会去看看的。非常感谢您的推荐!
    【解决方案2】:

    工作了大半天,结果发现不是机械化问题,它看起来更像是编码错误。经过大量的调整和诅咒,我已经让代码正常工作了。

    对于像我这样的未来 Google 员工,我在下面提供更新的代码:

    #manager.py [unchanged from original]
    def FetchReports(links,Username,Password,VendorID):
        import Fetch
        import multiprocessing
    
        pool = multiprocessing.Pool(processes=4, initializer=Fetch._ProcessStart, initargs=(SiteBase,DataPath,Username,Password,VendorID,))
        pool.map(Fetch.DownloadJob,_SplitLinksArray(links))
        pool.close()
        pool.join()
    
    
    #worker.py
    import mechanize
    from multiprocessing import current_process
    
    def _ProcessStart(_SiteBase,_DataPath,User,Password,VendorID):
        global cookies
        cookies = mechanize.LWPCookieJar()
        opener = mechanize.build_opener(mechanize.HTTPCookieProcessor(cookies))
    
        Login(User,Password,opener)
    
        global SiteBase
        SiteBase = _SiteBase
    
        global DataPath
        DataPath = _DataPath
    
        cookies.save(DataPath+'\\'+current_process().name+'cookies.txt',True,True)
    
    def DownloadJob(link):
        cj = mechanize.LWPCookieJar()
        cj.revert(filename=DataPath+'\\'+current_process().name+'cookies.txt',True,True)
        opener = mechanize.build_opener(mechanize.HTTPCookieProcessor(cj))
    
        mechanize.urlretrieve(url=mechanize.urljoin(SiteBase, link),filename=DataPath+'\\'+filename,data=data)
    

    因为我只是从列表中下载链接,mechanize 的非线程安全性质似乎不是问题[完全披露:我已经运行了这个过程恰好 3 次,所以在进一步测试中可能会出现问题] .多处理模块和它的工作池完成了所有繁重的工作。在文件中维护 cookie 对我来说很重要,因为我从中下载的网络服务器必须为每个线程提供它自己的会话 ID,但实现此代码的其他人可能不需要使用它。我确实注意到它似乎“忘记”了 init 调用和 run 调用之间的变量,所以 cookiejar 可能不会跳转。

    【讨论】:

    • 我的代码中还有一个错误,我将在以后的问题中发布,我的线程都没有正确退出。 atexit 函数在那里,但它不会触发,除非我将其更改为装饰器。但是,它首先丢失了我用来登录该站点的所有会话变量!目前,可以将八个会话挂起,但我将不得不在未来重新审视该程序。
    【解决方案3】:

    为了在第一个代码示例中启用cookie会话,将以下代码添加到函数DownloadJob

    cj = mechanize.LWPCookieJar()
    opener = mechanize.build_opener(mechanize.HTTPCookieProcessor(cj))
    mechanize.install_opener(opener)
    

    然后你可以照常检索网址:

    mechanize.urlretrieve(mechanize.urljoin(SiteBase, link),filename=DataPath+'\\'+filename,data=data)
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-03-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多