Python网页爬虫递归改造与动态队列全量遍历实现咨询
现有代码核心问题梳理
- 全局变量类型定义混乱:初始
queue为列表,但代码中误用set()执行类型转换、调用列表不存在的add()方法,后续又错误转回列表,实际运行会直接抛出类型错误 - 遍历逻辑硬编码初始长度:
main函数在循环启动时就将length绑定为当前队列长度,动态新增的队列元素无法被纳入遍历范围,导致爬取在处理完首批链接后就提前终止 - 队列去重逻辑缺失:新增链接时仅判断是否在待爬队列中,未和已爬取集合
crawled做交叉校验,容易出现重复入队、死循环爬取相同链接的问题
递归版实现方案
递归逻辑核心:每次从队列头部取出未爬取的链接完成处理后,立即递归调用自身继续处理剩余队列元素,直到队列为空时触发递归终止条件,不需要手动提前绑定遍历长度。
调整后的完整可运行代码如下:
import urllib.request from bs4 import BeautifulSoup from urllib.parse import urljoin # 规范处理链接拼接,替代手动字符串匹配 # 全局初始化 queue = [] # 待爬取队列 crawled = set() # 已爬取集合,支持O(1)时间复杂度查询 base_url = 'https://www.example.com' start_url = 'https://www.example.com/' header = { 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.11', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'Accept-Charset': 'ISO-8859-1,utf-8;q=0.7,*;q=0.3', 'Accept-Encoding': 'none', 'Accept-Language': 'en-US,en;q=0.8', 'Connection': 'keep-alive' } def get_http_response(url): # 跳过已处理过的链接 if url in crawled: return None try: request = urllib.request.Request(url, headers=header) response = urllib.request.urlopen(request, timeout=10) crawled.add(url) return response except Exception as e: print(f"请求失败 {url}: {str(e)}") crawled.add(url) # 请求失败的链接也标记为已处理,避免重复请求 return None def get_html_content(http_response): if not http_response: return None content = http_response.read() return BeautifulSoup(content, 'html.parser') def link_finder(html): if not html: return [] valid_new_links = [] for tag in html.select('[href]'): raw_href = tag['href'] # 自动识别绝对路径、相对路径、协议相对路径,自动拼接为完整URL full_url = urljoin(base_url, raw_href) # 三重校验:仅保留同域名、未爬取、未入队的有效链接 if full_url.startswith(base_url) and full_url not in crawled and full_url not in queue: valid_new_links.append(full_url) return valid_new_links def crawler_recursive(): # 递归终止条件:待爬队列为空时停止 if not queue: return # 取出队首待爬链接 current_url = queue.pop(0) # 完成当前链接的请求、解析、提取新链接全流程 response = get_http_response(current_url) html = get_html_content(response) new_links = link_finder(html) # 新发现的有效链接加入队列 queue.extend(new_links) # 递归处理下一个队列元素 crawler_recursive() def main(): # 初始化队列加入起始爬取地址 if start_url not in crawled: queue.append(start_url) crawler_recursive() print(f"爬取完成,共处理 {len(crawled)} 个页面") if __name__ == "__main__": main()
关键逻辑说明
- 递归终止条件直接判断队列是否为空,不需要手动计算固定遍历长度,队列动态新增的元素会自动被纳入后续处理流程
- 用
urljoin替代手动拼接链接,避免漏处理带查询参数、多级相对路径、协议相对路径的链接 - 三重去重校验机制:新链接同时排除已爬取集合、待爬队列中的重复项,避免无意义的重复请求
- 增加基础异常捕获,避免单个页面请求失败导致整个爬虫崩溃
注意:Python默认递归深度上限为1000,如果目标网站待爬链接量超过递归深度阈值,可将递归逻辑替换为等价的while循环实现,处理逻辑完全一致,不会出现栈溢出问题:
def crawler_loop(): while queue: current_url = queue.pop(0) response = get_http_response(current_url) html = get_html_content(response) new_links = link_finder(html) queue.extend(new_links)
内容的提问来源于stack exchange,提问作者Soumya Pandey
相关产品推荐
相关产品推荐

