Python + requests + splinter:发出多个并发“get”请求的最快/最佳方法是啥?

Posted

技术标签:

【中文标题】Python + requests + splinter:发出多个并发“get”请求的最快/最佳方法是啥?【英文标题】:Python + requests + splinter: What's the fastest/best way to make multiple concurrent 'get' requests?Python + requests + splinter:发出多个并发“get”请求的最快/最佳方法是什么? 【发布时间】:2017-10-04 05:13:15 【问题描述】:

目前正在和其他学生一起上网络爬虫课程,我们应该向一个虚拟站点发出“获取”请求,对其进行解析,然后访问另一个站点。

问题是,虚拟站点的内容只上线了几分钟就消失了,内容每隔一段时间就会重新上线。在内容可用期间,每个人都试图发出“获取”请求,所以我的只是挂起,直到每个人都清理干净,内容最终消失。所以我最终无法成功发出“获取”请求:

import requests
from splinter import Browser    

browser = Browser('chrome')

# Hangs here
requests.get('http://dummysite.ca').text
# Even if get is successful hangs here as well
browser.visit(parsed_url)

所以我的问题是,在我得到响应之前发出无休止的并发“获取”请求的最快/最佳方式是什么?

【问题讨论】:

您的请求的“速度”不是问题。问题似乎与服务器的响应能力(或缺乏响应能力)有关。在我看来,你无法可靠地赢得比赛,这取决于你和服务器之间的其他因素。充其量,您可以尝试发送许多并发请求,例如每秒左右,以提高您的机会。不过老实说,我认为这样的服务器没有理由不能同时处理班级中所有学生的请求,除非它是设计使然,或者服务器没有所需的资源或配置错误。 @sytech 感谢您的洞察力和回应。如果是这种情况,您介意展示如何按照建议发送许多并发请求吗?不妨试一试,这样我也可以投票/接受答案。 @sytech 检查您是否看过我之前的回复。提前谢谢! 相关SO问题:***.com/questions/43902093/… 这有点开箱即用,但是游说其他学生停止对资源严重不足的玩具服务器进行 DDoS 攻击,设置 Amazon Lambda(或类似)例程以偶尔复制其数据到无法处理流量的其他地方,然后点击 that? 【参考方案1】:

来自documentation for requests:

如果远程服务器很慢,你可以告诉 Requests 等待 永远响应,通过传递 None 作为超时值,然后 取一杯咖啡。

import requests

#Wait potentially forever
r = requests.get('http://dummysite.ca', timeout=None)

#Check the status code to see how the server is handling the request
print r.status_code

Status codes 以 2 开头表示请求已被接收、理解和接受。 200 表示请求成功并返回信息。但是 503 表示服务器过载或正在维护。

请求用于包括一个称为异步的模块,它可以发送并发请求。它现在是一个名为grequests 的独立模块 您可以使用它来无休止地发出并发请求,直到 200 响应:

import grequests

urls = [
'http://python-requests.org', #Just include one url if you want
'http://httpbin.org',
'http://python-guide.org',
'http://kennethreitz.com'
]

def keep_going():
    rs = (grequests.get(u) for u in urls) #Make a set of unsent Requests
    out = grequests.map(rs) #Send them all at the same time
    for i in out:
        if i.status_code == 200:
            print i.text
            del urls[out.index(i)] #If we have the content, delete the URL
            return

while urls:
    keep_going() 

【讨论】:

多次请求同一个URL的情况下应该怎么改?你能详细说明一下*10**8吗? 最后的代码部分可以与 urls 列表中的一个 url 一起正常工作。 *10**8 表示列表中位于它前面的 url 乘以 1 亿次。然后一次发送 100 个获取请求。 10**8 是 python 的 10 的 8 次方,即 100,000,000。抱歉,这只是服务器运行速度非常慢的一个示例。 或者您可以通过在 urls 中只有一个 url 并将列表乘以一个整数来发送并发 get 请求到同一个 url,例如urls = ['www.example.com']*10 在上面的最后一段代码中。 我已经删除了关于*10**8的部分答案。【参考方案2】:

在这种情况下,并发不会有太大帮助,因为服务器似乎是限制因素。一种解决方案是发送一个带有超时间隔的请求,如果已超过该间隔,则在几秒钟后再次尝试该请求。然后逐渐增加重试之间的时间,直到获得所需的数据。例如,您的代码可能如下所示:

import time
import requests

def get_content(url, timeout):
    # raise Timeout exception if more than x sends have passed
    resp = requests.get(url, timeout=timeout)
    # raise generic exception if request is unsuccessful
    if resp.status_code != 200:
        raise LookupError('status is not 200')
    return resp.content


timeout = 5 # seconds
retry_interval = 0
max_retry_interval = 120
while True:
    try:
        response = get_content('https://example.com', timeout=timeout)
        retry_interval = 0        # reset retry interval after success
        break
    except (LookupError, requests.exceptions.Timeout):
        retry_interval += 10
        if retry_interval > max_retry_interval:
            retry_interval = max_retry_interval
        time.sleep(retry_interval)

# process response

如果需要并发,请考虑 Scrapy 项目。它使用 Twisted 框架。在 Scrapy 中,您可以将 time.sleep 替换为 reactor.callLater(fn, *args, **kw) 或使用数百个中间件插件之一。

【讨论】:

想继续尝试,直到我得到我需要的信息。在那种情况下,并发不是更有效吗?另外,您能否详细说明以超时间隔发送意味着什么?这是否意味着如果请求在定义的超时间隔内没有得到任何响应,则该请求应该被丢弃?提前谢谢你 通常并发用于从多个 URL 获取内容,而不仅仅是单个 URL。或者,如果您在检索请求时还有其他任务可能正在运行。我不确定您是否会从您的用例的并发设计模式中获得任何好处。超时值是requests 等待响应的秒数,在此之后,请求将被丢弃。【参考方案3】:

Gevent 提供了一个运行异步网络请求的框架。

它可以修补 Python 的标准库,以便现有的库(如 requestssplinter)开箱即用。

下面是一个简短的示例,说明如何根据上述代码发出 10 个并发请求并获取它们的响应。

from gevent import monkey
monkey.patch_all()
import gevent.pool
import requests

pool = gevent.pool.Pool(size=10)
greenlets = [pool.spawn(requests.get, 'http://dummysite.ca')
             for _ in range(10)]
# Wait for all requests to complete
pool.join()
for greenlet in greenlets:
    # This will raise any exceptions raised by the request
    # Need to catch errors, or check if an exception was
    # thrown by checking `greenlet.exception`
    response = greenlet.get()
    text_response = response.text

也可以使用map 和响应处理函数来代替get

更多信息请参见gevent documentation。

【讨论】:

***.com/questions/2632520/… 怎么样? 怎么样? Gevent 可以轻松处理数以万计的请求。查看异步网络 I/O,尤其是与可用套接字数量、阻塞代码开销和事件循环的关系以了解更多信息。 有没有办法用splinter 做到这一点?因为,最终想用find_by_tag('option')之类的东西。 @Joko - 同样的方法与 splinter 或任何其他使用库的标准 python 库的工作方式相同。只需更改请求导入并将pool.spawn 的目标设为等效的分裂函数。【参考方案4】:

    决定使用requestssplinter

    了解Requests: HTTP for Humans 阅读Splinter

    相关

    了解keep-alive 阅读blocking-or-non-blocking 了解timeouts 阅读errors-and-exceptions

如果你能够得到不挂起个请求,你可以考虑重复的请求,例如:

while True:
    requests.get(...
    if request is succesfull:
        break

    time.sleep(1)

【讨论】:

***.com/questions/2632520/… 怎么样?如果我需要同时使用requestssplinters 怎么办?或者一个人能做其他人能做的一切? 您已经阅读了第 1 点,您可以决定哪一个适合您的需求。到目前为止,您只想检索 url 的内容。 您能澄清一下阅读第 1 点吗?而不仅仅是一个可行的解决方案 - 事实上我也有一个可行的解决方案,我实际上正在寻找最佳的解决方案,所以如果你能澄清每个建议的起起落落,我将不胜感激。 签到看看你是否看过我之前的评论。在您提供的示例中,requests.get(.. 不应该有一个结束 ) 吗? @JoKo:看三个点...,它还没有完成——它只是为了表明必须遵循一些事情

以上是关于Python + requests + splinter:发出多个并发“get”请求的最快/最佳方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章

Python+Requests接口测试教程:requests

接口_requests_基于python

python json requests request 模块

Python安装requests

python爬虫之requests

Python-爬虫-requests