我正在编写一个多线程的Web爬虫,每个线程的例程函数基本上是一个无限循环,并且有一个共享字典来存储那些已经访问过的url,并且我使用线程锁来同步。
我可能想用ctrl + c中断它有时候,我在线程运行函数中添加一个try catch来捕获键盘中断,之后我可能想做一些最终任务,例如将数据转储到数据库或pkl文件。
问题在于,每次我打断它时,它都无法进入捕获过程,有时它只是卡在那里,有时它仍然继续运行。
那么在多线程编程中处理异常/中断的最佳做法是什么?
我把我的代码如下:
from bs4 import BeautifulSoup
import requests
import threading
import queue
import pickle
import os
from concurrent.futures import ThreadPoolExecutor
worker_num = 8
q = queue.Queue()
lock = threading.Lock()
if os.path.exists('./checked.pkl'):
with open('./checked.pkl', 'rb') as f:
checked = pickle.load(f)
else:
checked = set()
def get_links(url):
# do sth....
def run():
print(threading.current_thread())
try:
while True:
next_url = q.get()
links = get_links(next_url)
lock.acquire()
for link in links:
if link not in checked:
q.put(link)
print(len(checked))
lock.release()
except Exception as e:
print(e)
print('interrupt')
lock.acquire()
with open('./checked.pkl', 'wb') as f:
pickle.dump(checked, f)
lock.release()
if __name__ == '__main__':
q.put(start_url)
with ThreadPoolExecutor(worker_num) as executor:
for _ in range(worker_num):
executor.submit(run)
答案 0 :(得分:2)
KeyboardInterrupt
总是在主线程中引发。你无法在另一个线程中处理它。
你应该在每个成功的工作块之后检查你的状态(它不必在每个URL之后,但它也可能是)。这将为您提供状态更新,即使您的进程因其他原因崩溃(一些不可理解的原因 - 如段错误或整个主机崩溃)。您还应该以原子方式编写检查点,以便在更新中期崩溃时,您不会以腐败,无法使用的状态结束。 (由于其不安全性,脆弱性和跨语言挑战,您也不应该使用pickle作为检查点状态格式。)
在正常操作期间安全地定期写入状态更新后,可以通过将工作线程转换为守护程序线程并将KeyboardInterrupt
处理添加到主线程来使爬网程序可中断。在这一点上,如果工作线程被杀死而没有机会进行清理(如果你让它们成为守护程序线程并让主线程退出就会发生),因为它们最近会检查它们的状态,这无关紧要。
此外,您应该考虑使用现有的抓取工具,例如Scrapy,而不是自己滚动。