我用一个非常简单的蜘蛛程序从单个站点获取网页。
这是最小化版本。
from twisted.internet import epollreactor
epollreactor.install()
from twisted.internet import reactor
from twisted.web.client import Agent, HTTPConnectionPool, readBody
baseUrl = 'http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode='
start = 1001
end = 3500
pool = HTTPConnectionPool(reactor)
pool.maxPersistentPerHost = 10
agent = Agent(reactor, pool=pool)
def onHeader(response, i):
deferred = readBody(response)
deferred.addCallback(onBody, i)
deferred.addErrback(errorHandler)
return response
def onBody(body, i):
print('Received %s, Length %s' % (i, len(body)))
def errorHandler(err):
print('%s : %s' % (reactor.seconds() - startTimeStamp, err))
def requestFactory():
for i in range (start, end):
deferred = agent.request('GET', baseUrl + str(i))
deferred.addCallback(onHeader, i)
deferred.addErrback(errorHandler)
print('Generated %s' % i)
reactor.iterate(1)
print('All requests has generated, elpased %s' % (reactor.seconds() - startTimeStamp))
startTimeStamp = reactor.seconds()
reactor.callWhenRunning(requestFactory)
reactor.run()
对于一些请求,如100,它工作正常。但对于大量请求,它将失败。
我希望所有请求(大约3000个)都应自动汇集,调度和流水线化,因为我使用HTTPConnectionPool
,设置maxPersistentPerHost
,用它创建一个Agent
实例并逐步增加创建连接。
但事实并非如此,这些联系并不是保持活力,也不是合并。
在这个程序中,它确实以增量方式建立连接,但连接没有汇集,每个连接都会在收到主体后关闭,后来的请求永远不会在池中等待可用的连接。
因此,它需要数千个套接字,并且最终因超时而失败,因为远程服务器的连接超时设置为30秒。成千上万的请求不能在30秒内完成。
你能帮我个忙吗?
我已经尽力了,这是我的发现。
Connection: Keep-Alive
,也可以执行响应标头。Connection: Keep-Alive
,但响应标头仍然不是。 (我确信我的服务器行为正常,其他内容如Chrome,wget确实收到了Connection: Keep-Alive
标题。) /proc/net/sockstat
,它在开始时迅速增加,之后迅速减少。 (我已经增加了ulimit以支持大量的套接字) Connection: Keep-Alive
永远不会出现在响应标头中。基于以上所有内容,我对quirk Connection: Keep-Alive
标题破坏程序非常怀疑。但是这个标头是HTTP 1.1标准的一部分,它确实报告为HTTP 1.1。我对此完全感到困惑。
答案 0 :(得分:2)
我自己解决了这个问题,得到了IRC的帮助和stackoverflow中的另一个问题,Queue remote calls to a Python Twisted perspective broker?
总之,代理的行为与Nodejs中的行为非常不同(我在Nodejs方面有一些经验)。正如Nodejs doc
所述agent.requests
包含尚未分配给套接字的请求队列的对象。
agent.maxSockets
默认设置为5.确定代理可以为每个源打开多少个并发套接字。 Origin可以是'host:port'或'host:port:localAddress'组合。
扭曲:
HTTPConnectionPool
实例进行构造,则代理可以对请求进行排队。maxPersistentPerHost
的NodeJS:
agent.requests
变量,等待可用连接。 代理的扭曲行为导致代理能够对请求进行排队,但实际上却没有。
按照我们的直觉,一旦将连接池分配给代理,它就符合代理只使用池中的连接的直觉,并且如果池已经用完则等待可用连接。这与Nodejs中的代理完全匹配。
就我个人而言,我认为这是一种扭曲的错误行为,或者至少可以改进以提供设置代理行为的选项。
根据这一点,我必须使用DeferredSemaphore
手动安排请求。
我向github上的treq
项目提出了一个问题,并获得了类似的解决方案。 https://github.com/dreid/treq/issues/71
这是我的解决方案。
#!/usr/bin/env python
from twisted.internet import epollreactor
epollreactor.install()
from twisted.internet import reactor
from twisted.web.client import Agent, HTTPConnectionPool, readBody
from twisted.internet.defer import DeferredSemaphore
baseUrl = 'http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode='
start = 1001
end = 3500
count = end - start
concurrency = 10
pool = HTTPConnectionPool(reactor)
pool.maxPersistentPerHost = concurrency
agent = Agent(reactor, pool=pool)
sem = DeferredSemaphore(concurrency)
done = 0
def onHeader(response, i):
deferred = readBody(response)
deferred.addCallback(onBody, i)
deferred.addErrback(errorHandler, i)
return deferred
def onBody(body, i):
sem.release()
global done, count
done += 1
print('Received %s, Length %s, Done %s' % (i, len(body), done))
if(done == count):
print('All items fetched')
reactor.stop()
def errorHandler(err, i):
print('[%s] id %s: %s' % (reactor.seconds() - startTimeStamp, i, err))
def requestFactory(token, i):
deferred = agent.request('GET', baseUrl + str(i))
deferred.addCallback(onHeader, i)
deferred.addErrback(errorHandler, i)
print('Request send %s' % i)
#this function it self is a callback emit by reactor, so needn't iterate manually
#reactor.iterate(1)
return deferred
def assign():
for i in range (start, end):
sem.acquire().addCallback(requestFactory, i)
startTimeStamp = reactor.seconds()
reactor.callWhenRunning(assign)
reactor.run()
是不是?请求指出我的错误和改进。
答案 1 :(得分:0)
对于一些请求,如100,它工作正常。但对于大量请求, 它会失败。
这是针对网络抓取工具的保护或针对DoS / DDoS的服务器保护,因为您在短时间内从同一IP发送了太多请求,因此防火墙或WSA将阻止您将来的请求。只需修改您的脚本,即可在一段时间内批量生成请求。你可以在每个X请求后的一段时间内使用callLater()。