【问题标题】:Scraping with a multithreaded queue + urllib3 suffers a drastic slowdown使用多线程队列 + urllib3 进行抓取会严重减速
【发布时间】:2016-02-08 20:43:19
【问题描述】:

我试图在尽可能短的时间内抓取大量包含 JSON 格式数据的 URL(大约 300 万个)。为了实现这一点,我有一个使用队列、多线程和 Urllib3 的 Python 代码(python 3)。前 3 分钟一切正常,然后代码开始变慢,然后似乎完全卡住了。我已经阅读了我能找到的关于这个问题的所有内容,但不幸的是,解决方案似乎需要远超我的知识。

我试图限制线程数:它没有解决任何问题。我还尝试限制队列的最大大小并更改套接字超时,但它也没有帮助。远程服务器没有阻止我,也没有将我列入黑名单,因为我可以随时重新启动我的脚本,并在开始时取得良好的效果(代码在相当随机的时间开始变慢)。此外,有时我的互联网连接似乎被切断 - 因为我无法在任何网站上冲浪 - 但这个特定问题并非每次都出现。

这是我的代码(请放轻松,我是初学者):

#!/usr/bin/env python
import urllib3,json,csv
from queue import Queue
from threading import Thread

csvFile =  open("X.csv",  'wt',newline="")
writer  =  csv.writer(csvFile,delimiter=";")
         writer.writerow(('A','B','C','D'))

def do_stuff(q):
    http = urllib3.connectionpool.connection_from_url('http://www.XXYX.com/',maxsize=30,timeout=20,block=True)

    while True:

        try:

            url = q.get()
            url1 = http.request('GET',url)      

            doc = json.loads(url1.data.decode('utf8'))

            writer.writerow((doc['A'],doc['B'], doc['C'],doc['D']))

        except:
             print(url)

        finally:
            q.task_done()

q = Queue(maxsize=200)
num_threads = 15

for i in range(num_threads):
    worker = Thread(target=do_stuff, args=(q,))
    worker.setDaemon(True)
    worker.start()

for x in range(1,3000000):
    if x < 10:
        url = "http://www.XXYX.com/?i=" + str(x) + "&plot=short&r=json"
    elif x < 100:
        url = "http://www.XXYX.com/?i=tt00000" + str(x) + "&plot=short&r=json"
    elif x < 1000:
        url = "http://www.XXYX.com/?i=0" + str(x) + "&plot=short&r=json"
    elif x < 10000:
        url = "http://www.XXYX.com/?i=00" + str(x) + "&plot=short&r=json"
    elif x < 100000:
        url = "http://www.XXYX.com/?i=000" + str(x) + "&plot=short&r=json"
    elif x < 1000000:
        url = "http://www.XXYX.com/?i=0000" + str(x) + "&plot=short&r=json"
    else:
        url = "http://www.XXYX.com/?i=00000" + str(x) + "&plot=short&r=json"

    q.put(url)

q.join()    
csvFile.close()
print("done")

【问题讨论】:

  • 我看不出您的代码有任何明显错误。您正在锤击的服务器很可能正在限制/阻止您的连接。确保您尊重 robots.txt 并具有合理的速率限制或与网站有明确的 SLA 以进行抓取。否则,要解决自动限制,您可能需要从多个 IP 地址进行爬网。
  • 你认为在 do_stuff() 中实现睡眠时间就足够了吗?如果是,您认为我多久才能顺利结束爬行?

标签: multithreading python-3.x web-scraping queue urllib3


【解决方案1】:

正如 shazow 所说,这不是线程的问题,而是每个线程从服务器获取数据的超时时间。尝试在您的代码中包含一些超时:

finally:
    sleep(50)
    q.task_done()

还可以通过生成自适应超时来改进它,例如您可以测量您成功获得了多少数据,如果该数字减少,则增加睡眠时间,反之亦然

【讨论】:

  • 这很有趣。我将监控我在 1 分钟内/每个线程中获得的数据。如果该数字减少,线程将休眠数秒 * 它已经休眠的时间(因此休眠时间将根据线程的性能增加 - 如有必要) - 否则它将继续工作并重置暂停次数为一。
  • @lo-bellin,我认为选择较低的间隔不会对性能产生太大影响,例如 5 秒。 time 库中添加了函数perf_counterprocess_time,它们具有很高的准确性。例如,算法可以使用公式 sleep_t = const / (took2 - take1) 计算睡眠时间,这样它就可以处理处理时间的导数(这正是执行速度)。这样算法应该是灵活的。
  • 我相信我们得到了“远远超出我的知识”的东西。你介意给我举一个更具体的例子吗?因为我不明白“sleep_t = const / (took2 - take1)”如何确保睡眠时间足够代码不会锤击网站——
  • @LoBellin,在该公式中,take2 是当前处理时间,而 take1 是以前的。用纯数学术语更好地解释:数据传输速度是传输的数据量(字节)的导数(按时间),速度 = dData / dT。因为你不能直接计算导数,它可以近似地用增量代替:速度≈ΔData/ΔT。它可以使用两个点 (Data2, t2) 和 (Data1, t1) 来计算,这给您 Speed ≈ (Data2 - Data1) / (t2 - t1),其中 Data1 和 Data2 是在时间 t2 传输的总字节数和t1
  • @LoBellin,因为在一个周期(例如每 5 秒)测量字节量似乎更好,这给你的时间如下:0 秒(开始),5 秒(第一次获取),10 sec(第二次获取)等等,因此对于导数 (Speed) 的第一个值,它将是 Speed1 ≈ (Data2 - Data1) / (5 - 0),对于第二个 Speed2 ≈ (Data3 - Data2) / (10 - 5 ) 等等。在这里您可以看到分隔符是恒定的(在本例中为 5)
猜你喜欢
  • 2018-04-05
  • 1970-01-01
  • 1970-01-01
  • 2020-05-24
  • 1970-01-01
  • 1970-01-01
  • 2019-08-03
  • 2020-11-07
  • 2011-06-12
相关资源
最近更新 更多