我想以递归方式抓取托管数千个文件的Web服务器,然后检查它们是否与本地存储库中的内容不同(这是检查传递基础结构中的错误的一部分)。 到目前为止,我一直在玩各种原型,这是我注意到的。如果我进行简单的递归并将所有文件放入列表中,操作将在大约230秒内完成。请注意,我每个目录只发出一个请求,因此实际下载我感兴趣的其他文件是有意义的:
def recurse_links(base):
result = []
try:
f = urllib.request.urlopen(base)
soup = BeautifulSoup(f.read(), "html.parser")
for anchor in soup.find_all('a'):
href = anchor.get('href')
if href.startswith('/') or href.startswith('..'):
pass
elif href.endswith('/'):
recurse_links(base + href)
else:
result.append(base + href)
except urllib.error.HTTPError as httperr:
print('HTTP Error in ' + base + ': ' + str(httperr))
我想,如果我可以在爬虫仍在工作时开始处理我感兴趣的文件,我可以节省时间。所以接下来我尝试的是一个可以进一步用作协程的发生器。发电机耗时260秒,稍微多一点,但仍然可以接受。这是发电机:
def recurse_links_gen(base):
try:
f = urllib.request.urlopen(base)
soup = BeautifulSoup(f.read(), "html.parser")
for anchor in soup.find_all('a'):
href = anchor.get('href')
if href.startswith('/') or href.startswith('..'):
pass
elif href.endswith('/'):
yield from recurse_links_gen(base + href)
else:
yield base + href
except urllib.error.HTTPError as http_error:
print(f'HTTP Error in {base}: {http_error}')
回答评论部分提出的一些问题:
最后,我决定给生产者/消费者/队列一个镜头,一个简单的PoC运行4倍,同时加载100%的一个CPU核心。以下是简要代码(爬虫与上面的基于生成器的爬虫相同):
class ProducerThread(threading.Thread):
def __init__(self, done_event, url_queue, crawler, name):
super().__init__()
self._logger = logging.getLogger(__name__)
self.name = name
self._queue = url_queue
self._crawler = crawler
self._event = done_event
def run(self):
for file_url in self._crawler.crawl():
try:
self._queue.put(file_url)
except Exception as ex:
self._logger.error(ex)
所以这是我的问题:
threading
库创建的线程是否实际上是线程,是否有办法让它们实际分布在各种CPU内核之间?答案 0 :(得分:1)
1)使用线程库创建的线程是否实际上是线程,是否有办法在各种CPU内核之间实际分配?
是的,这些是线程,但要使用CPU的多个内核,您需要使用multiprocessing包。
2)我认为大量的性能下降来自生产者等待将项目放入队列。但这可以避免吗?
这取决于您创建的线程数,一个原因可能是由于上下文切换,您的线程正在进行。螺纹的最佳值应为2/3,即创建2/3螺纹并再次检查性能。
3)发生器是否较慢,因为它必须保存功能上下文然后反复加载?
生成器并不慢,对你正在处理的问题来说相当好,因为你找到了一个url,你把它放到队列中。
4)当爬虫仍在填充队列/列表/其他内容时,开始实际使用这些文件的最佳方法是什么,从而使整个程序更快?
创建一个ConsumerThread类,它从队列中获取数据(在您的情况下为url)并开始处理它。