You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

求助:如何用多线程从单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()

额外说明

  1. 代码仅爬取TradeIndia域名下的链接,避免无限制爬取外部网站,可根据需求调整过滤规则
  2. 线程数设置为5,可根据服务器响应情况调整,避免因请求过于频繁被封禁
  3. 爬取完成的判断逻辑为“连续两次检查无新增爬取URL”,可根据实际情况优化
  4. 若需要更精准的全量锚链接收集,可在process_page中维护一个线程安全的all_links集合,每次提取链接后添加进去

内容的提问来源于stack exchange,提问作者Sam

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 18:43:25