求助:如何用多线程从单URL提取锚标签及嵌套锚标签href值?现有代码仅获单页结果
改进多线程递归爬取TradeIndia网站锚链接的方案
原代码仅完成两层爬取(初始页面 + 初始页面直接链接的页面),未递归处理后续页面的新链接,同时存在URL不规范、无爬取去重、反爬应对不足等问题,导致无法获取完整的锚链接。以下是针对性改进方案:
核心改进点
- 递归爬取+已爬URL去重:维护已爬URL集合,避免重复请求;新提取的链接过滤后动态提交线程任务
- URL规范化:将相对路径转为绝对URL,确保请求有效性
- 反爬优化:添加浏览器请求头,降低被拦截概率
- 精细化异常处理:捕获HTTP错误、超时、解析异常等,提升稳定性
修改后的完整代码
import requests from bs4 import BeautifulSoup from concurrent.futures import ThreadPoolExecutor from urllib.parse import urljoin import time from threading import Lock # 全局变量:已爬取URL集合和锁(线程安全) crawled_urls = set() url_lock = Lock() base_url = "https://www.tradeindia.com/" def get_page(url): # 添加浏览器请求头,模拟正常访问 headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36" } try: # 添加超时设置,避免请求挂起 response = requests.get(url, headers=headers, timeout=10) response.raise_for_status() # 抛出HTTP错误状态码异常 return response.content except Exception as e: print(f"请求失败 {url}: {str(e)}") return None def extract_links(html_content, current_url): if not html_content: return [] soup = BeautifulSoup(html_content, 'html.parser') links = [] for a in soup.find_all('a', href=True): href = a['href'] # 转换为绝对URL,过滤无效链接 absolute_url = urljoin(current_url, href) # 仅保留TradeIndia域名下的链接,避免爬取外部网站 if absolute_url.startswith(base_url): links.append(absolute_url) return links def process_page(url, executor): # 线程安全地检查并标记URL为已爬取 with url_lock: if url in crawled_urls: return crawled_urls.add(url) print(f"正在爬取: {url}") html_content = get_page(url) links = extract_links(html_content, url) # 线程安全地过滤未爬取的链接,并提交新任务 with url_lock: new_urls = [link for link in links if link not in crawled_urls] for new_url in new_urls: executor.submit(process_page, new_url, executor) return links def main(): start_url = base_url # 使用线程池,设置合理的最大线程数(避免给服务器造成过大压力) with ThreadPoolExecutor(max_workers=5) as executor: # 提交初始页面任务 executor.submit(process_page, start_url, executor) # 等待所有任务完成(通过监控已爬URL数量变化判断) last_size = 0 while True: with url_lock: current_size = len(crawled_urls) if current_size == last_size: # 连续两次检查无新增爬取URL,认为爬取完成 time.sleep(2) with url_lock: if len(crawled_urls) == current_size: break last_size = current_size time.sleep(1) # 输出结果 print("\n所有提取的链接:") print(f"总链接数: {len(crawled_urls)}") for link in crawled_urls: print(link) if __name__ == "__main__": main()
额外说明
- 代码仅爬取TradeIndia域名下的链接,避免无限制爬取外部网站,可根据需求调整过滤规则
- 线程数设置为5,可根据服务器响应情况调整,避免因请求过于频繁被封禁
- 爬取完成的判断逻辑为“连续两次检查无新增爬取URL”,可根据实际情况优化
- 若需要更精准的全量锚链接收集,可在
process_page中维护一个线程安全的all_links集合,每次提取链接后添加进去
内容的提问来源于stack exchange,提问作者Sam
相关产品推荐
相关产品推荐

