页面的链接和该子页面的链接。递归/线程
Posted
技术标签:
【中文标题】页面的链接和该子页面的链接。递归/线程【英文标题】:Links of a page and links of that subpages. Recursion/Threads 【发布时间】:2019-04-23 10:56:14 【问题描述】:我正在制作一个下载网站内容的函数,然后我在站点中查找链接,并且对于每个链接,我都会递归调用相同的函数,直到第 7 级。问题是这需要很多时间,所以我希望使用线程池来管理此调用,但我不知道如何准确地将这些任务划分到线程池中。
这是我的实际代码,没有线程池。
import requests
import re
url = 'https://masdemx.com/category/creatividad/?fbclid=IwAR0G2AQa7QUzI-fsgRn3VOl5oejXKlC_JlfvUGBJf9xjQ4gcBsyHinYiOt8'
def searchLinks(url,level):
print("level: "+str(level))
if(level==3):
return 0
response = requests.get(url)
enlaces = re.findall(r'<a href="(.*?)"',str(response.text))
for en in enlaces:
if (en[0] == "/" or en[0]=="#"):
en= url+en[1:]
print(en)
searchLinks(en,level+1)
searchLinks(url,1)
【问题讨论】:
【参考方案1】:对于初学者,请注意这可能是一项大手术。例如,如果每个页面平均只有 10 个唯一链接,那么如果您想要递归 7 层深度,您将看到超过 1000 万个请求。
另外,我会使用像 BeautifulSoup 这样的 html 解析库,而不是正则表达式,这是一种抓取 HTML 的脆弱方法。避免打印到标准输出,这也会减慢工作速度。
至于线程,一种方法是使用工作队列。 Python 的queue class 是线程安全的,因此您可以创建一个工作线程池,用于轮询以从队列中检索 URL。当线程获取 URL 时,它会查找页面上的所有链接并将相关 URL(或页面数据,如果您愿意)附加到全局列表(在 CPython 上是 thread-safe operation - 对于其他实现,使用锁定共享数据结构)。这些 URL 被排入工作队列并继续处理。
线程在级别达到 0 时退出,因为我们使用的是 BFS 而不是使用堆栈的 DFS。这里(可能是安全的)假设是链接的级别比深度多。
并行性来自阻塞等待请求响应的线程,允许 CPU 运行另一个响应到达的线程来执行 HTML 解析和排队工作。
如果您想在多核上运行以帮助并行化工作负载中受 CPU 限制的部分,请read this blog post about the GIL 并查看生成进程。但单单线程就可以让您在很大程度上实现并行化,因为瓶颈受 I/O 限制(等待 HTTP 请求)。
下面是一些示例代码:
import queue
import requests
import threading
import time
from bs4 import BeautifulSoup
def search_links(q, urls, seen):
while 1:
try:
url, level = q.get()
except queue.Empty:
continue
if level <= 0:
break
try:
soup = BeautifulSoup(requests.get(url).text, "lxml")
for x in soup.find_all("a", href=True):
link = x["href"]
if link and link[0] in "#/":
link = url + link[1:]
if link not in seen:
seen.add(link)
urls.append(link)
q.put((link, level - 1))
except (requests.exceptions.InvalidSchema,
requests.exceptions.ConnectionError):
pass
if __name__ == "__main__":
levels = 2
workers = 10
start_url = "https://masdemx.com/category/creatividad/?fbclid=IwAR0G2AQa7QUzI-fsgRn3VOl5oejXKlC_JlfvUGBJf9xjQ4gcBsyHinYiOt8"
seen = set()
urls = []
threads = []
q = queue.Queue()
q.put((start_url, levels))
start = time.time()
for _ in range(workers):
t = threading.Thread(target=search_links, args=(q, urls, seen))
threads.append(t)
t.daemon = True
t.start()
for thread in threads:
thread.join()
print(f"Found len(urls) URLs using workers workers "
f"levels levels deep in time.time() - starts")
以下是在我不是特别快的机器上运行的一些示例:
$ python thread_req.py
Found 762 URLs using 15 workers 2 levels deep in 33.625585317611694s
$ python thread_req.py
Found 762 URLs using 10 workers 2 levels deep in 42.211519956588745s
$ python thread_req.py
Found 762 URLs using 1 workers 2 levels deep in 105.16120409965515s
在这个小规模运行中,性能提升了 3 倍。我在较大的运行中遇到了最大请求错误,所以这只是一个玩具示例。
【讨论】:
以上是关于页面的链接和该子页面的链接。递归/线程的主要内容,如果未能解决你的问题,请参考以下文章
Layuilayui中iframe子页面中的链接点击后在父页面中动态添加tab选项