我一直在玩多处理,我的代码在查看较小的数字时工作,但是当我想运行更大的样本时,会发生两件事:代码锁定或者我收到以下错误消息:“urlopen error [Errno 10054]现有连接被远程主机强行关闭“。我无法弄清楚如何让它发挥作用。感谢。
from multiprocessing import cpu_count
import urllib2
from bs4 import BeautifulSoup
import json
import timeit
import socket
import errno
def parseWeb(id):
url = 'https://carhood.com.au/rent/car_detail/'+str(id)+'/'
hdr = {'Accept': 'text/html,application/xhtml+xml,*/*',"user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.116 Safari/537.36"}
html = urllib2.urlopen(url).read()
soup=BeautifulSoup(html,"lxml")
car=soup.find("h1",{"class":"intro-title intro-title-tertiary"}).text
return car
if __name__ == '__main__':
start = timeit.default_timer()
pool = Pool(cpu_count()*100)
#This works for me when xrange(1,70)
results=pool.map(parseWeb,xrange(1,400))
print results
##I've tried this as a solution but it didn't work
## startNum=1
## endNum=470
## for x in range(startNum,endNum,70):
## print x
## results=pool.map(parseWeb,xrange(startNum,x))
## print results
## startNum=x
stop = timeit.default_timer()
print stop - start
答案 0 :(得分:2)
John Zwinck在问题评论中的建议是相当不错的。
部分问题是您无法控制接收服务器。当您放置过多的进程时,您强制另一端的服务器找出正确的处理方式您的所有请求一次。这导致您的进程闲置等待服务器在某个时刻返回它们 - 因为pool.map()
仅在所有进程完成时完成(它是阻止调用),这意味着只要服务器为每个服务器提供服务,您就会等待。
现在一切都取决于服务器。
服务器可以选择将其资源专用于逐个提供所有请求 - 这实际上意味着您的请求现在正在队列中等待,没有比您拥有的更好的优势只是一个接一个地连续发送您的请求。单线程服务器可以像这样建模,尽管它们的主要加速来自于它们是异步的并且在请求和请求之间快速跳跃。
某些服务器通常只有少量的进程或线程产生大量的子线程,这些子线程一个接一个地处理传入的请求 - 例如Apache服务器starts off with 2 dedicated processes with 25 threads each,所以理论上它可以处理50个并发请求,并按照配置的最高比例进行扩展。它将在此时尽可能多地服务,并将剩余的多余请求置于保持状态或拒绝服务。
如果某些服务器威胁到系统过载或者内部超时到达,它们将简单地终止或关闭连接。后者更有可能并且更经常遇到。
它的另一个方面就是您自己的CPU核心无法处理您要求他们执行的操作。核心可以处理一个一次线程 - 当我们谈到并行性时,我们真的在谈论同时处理线程的多个核心。具有大量较小线程的进程可以将这些线程分布在不同的CPU内核中,因此您可以从中受益。
但是你有一百个进程,每个进程都会引发阻塞 I / O调用(urlopen
为blocking)。如果该I / O调用立即响应,那么好 - 如果没有,现在其他进程正在等待这个过程完成,占用一个有价值的CPU核心。您已成功将等待引入到您希望显式避免等待的系统中。如果将此问题与接收服务器上的压力相结合,您会发现源于打开连接的一些延迟。
有很多解决方案,但在我看来,它们都归结为同样的事情:
避免阻止来电。使用触发请求的解决方案,将负责该操作的线程置于休眠状态并关闭调度程序运行队列,并在注册事件时将其唤醒。
使用异步性对您有利。单个线程可以在不阻塞的情况下生成多个请求,您只需要能够智能地处理响应,因为它们逐个进入。您甚至可以将响应传递给其他未完成任何工作的线程(例如,使用Queue
)。诀窍是让他们无缝地协同工作。
multiprocessing
虽然是处理流程的一个很好的解决方案,但它不是用于处理HTTP请求之间的交互和流程的适当行为的捆绑式解决方案。这是你通常必须自己编写的逻辑,如果你能更好地控制urlopen
的工作方式,可以完成 - 你必须找到一种方法来制作确保urlopen
没有阻止,或者至少愿意在发送请求后立即订阅事件通知。
当然,这一切都可以完成 - 但网络抓取是一个已解决的问题,并且无需重写轮子。
相反,有几个选项经过了尝试和测试:
asyncio
是Python 3.5的标准。虽然不是一个成熟的HTTP服务,但它为I / O绑定操作提供异步支持。您可以使用aiohttp
发出HTTP请求。这里有一个关于如何刮掉它的tutorial。
Scrapy在Python 2.7和Python 3上是可行的。它使用Twisted,asyncio
的非标准先行者和首选工具用于快速网络请求。我之所以提到Scrapy而不是Twisted,只是因为Scrapy已经为您处理了基础架构[可以阅读here] - 如果您愿意,您当然应该探索Twisted以了解底层系统。这是我在这里提到的所有解决方案中最容易掌握的,但在我的经验中,也是最有效的解决方案。
grequests
是受欢迎的requests
库的扩展(偶然优于urllib2
并且应该在每次机会时使用)以支持所谓的协同程序:线程可以在执行的多个点暂停和恢复,非常理想,如果您希望线程在等待I / O响应时工作。 grequests
构建在gevent
(协程库)之上,允许您在单个线程中发出多个请求,并按照自己的进度处理它们。