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

如何可靠发现非标准位置的站点地图?Python爬虫优化需求

SEO站点地图发现爬虫优化方案

一、扩展站点地图发现技巧

除了robots.txt和通用路径,这些实用方法能覆盖更多场景:

  • 解析首页HTML的<link rel="sitemap">标签:很多站点会在首页<head>中添加该标签指向站点地图
  • 尝试压缩格式:优先探测.xml.gz、.txt.gz后缀的路径,不少站点会用压缩版减少带宽消耗
  • HEAD转GET降级:遇到HEAD请求返回405/404时,改用带Range: bytes=0-0的GET请求(仅获取响应头,避免下载大文件)
  • 跟踪重定向:开启请求的自动重定向,捕获3xx响应后的最终URL,部分站点地图会藏在重定向后的位置
  • 挖掘CMS专属路径:不同CMS有默认的站点地图位置(见下文优先级列表)
  • 分析robots.txt的Disallow规则:尝试在允许爬取的目录下追加sitemap.xml等后缀,部分站点会把地图放在内容目录下

二、Python代码改进实现

核心改进点

  1. 异步并发探测:用aiohttp提升探测效率,配合信号量控制并发数
  2. HEAD→GET降级逻辑:解决部分服务器对HEAD响应不可靠的问题
  3. 扩充优先级路径列表:整合通用+CMS专属路径
  4. 自动去重与重定向跟踪:避免重复处理同一资源
  5. 压缩站点地图解析支持:处理.gz格式的地图文件

完整代码片段

import asyncio
import aiohttp
import gzip
import xml.etree.ElementTree as ET
from urllib.parse import urljoin, urlparse
from robots import RobotFileParser

# 候选路径列表(按优先级从高到低排序)
CANDIDATE_PATHS = [
    # 通用高频路径
    "/sitemap.xml",
    "/sitemap.txt",
    "/sitemap.xml.gz",
    "/sitemap_index.xml",
    "/sitemap-index.xml",
    # WordPress专属(WP5.5+默认路径优先)
    "/wp-sitemap.xml",
    "/wp-sitemap-index.xml",
    "/wp-content/sitemap.xml",
    "/sitemap-posttype-post.xml",
    # Shopify专属
    "/sitemap_products.xml",
    "/sitemap_collections.xml",
    # Joomla专属
    "/component/sitemap/",
    "/index.php?option=com_sitemap",
    # Drupal专属
    "/sitemap",
    "/sitemap.xml.gz",
]

async def fetch_sitemap_urls(base_url, session, semaphore):
    sitemap_urls = set()
    parsed_base = urlparse(base_url)
    base_domain = f"{parsed_base.scheme}://{parsed_base.netloc}"

    # 1. 解析robots.txt提取站点地图
    robots_url = urljoin(base_domain, "/robots.txt")
    rp = RobotFileParser()
    rp.set_url(robots_url)
    try:
        rp.read()
        for sitemap in rp.site_maps():
            sitemap_urls.add(sitemap)
    except Exception as e:
        print(f"[WARN] 解析robots.txt失败: {e}")

    # 2. 并发探测所有候选路径
    async def probe_single_path(path):
        target_url = urljoin(base_domain, path)
        async with semaphore:
            # 先尝试HEAD请求(轻量探测)
            try:
                async with session.head(target_url, allow_redirects=True, timeout=10) as resp:
                    if resp.status in (200, 301, 302):
                        sitemap_urls.add(str(resp.url))
                        return
            except (aiohttp.ClientError, asyncio.TimeoutError):
                pass

            # HEAD失败,降级为带Range的GET请求(仅获取响应头)
            try:
                headers = {"Range": "bytes=0-0"}
                async with session.get(target_url, allow_redirects=True, timeout=10, headers=headers) as resp:
                    if resp.status in (200, 301, 302):
                        sitemap_urls.add(str(resp.url))
            except (aiohttp.ClientError, asyncio.TimeoutError):
                print(f"[WARN] 探测路径失败: {target_url}")

    tasks = [probe_single_path(path) for path in CANDIDATE_PATHS]
    await asyncio.gather(*tasks)

    return list(sitemap_urls)

async def parse_sitemap_content(sitemap_url, session):
    """解析站点地图内容(支持压缩格式)"""
    parsed_url = urlparse(sitemap_url)
    content = None

    try:
        async with session.get(sitemap_url, timeout=15) as resp:
            if resp.status != 200:
                print(f"[ERROR] 无法获取站点地图: {sitemap_url}")
                return []

            content = await resp.read()
            # 处理压缩格式
            if parsed_url.path.endswith(".gz"):
                try:
                    content = gzip.decompress(content)
                except gzip.BadGzipFile:
                    print(f"[WARN] 不是有效的GZIP文件: {sitemap_url}")
                    return []

            # 解析XML格式的站点地图
            root = ET.fromstring(content)
            ns = {"sm": "http://www.sitemaps.org/schemas/sitemap/0.9"}
            urls = [elem.text for elem in root.findall(".//sm:loc", ns)]
            return urls
    except Exception as e:
        print(f"[ERROR] 解析站点地图失败: {sitemap_url}, 错误: {e}")
        return []

async def main():
    target_url = "https://example.com"
    # 速率控制:每个域名同时最多2个请求,避免给服务器造成压力
    semaphore = asyncio.Semaphore(2)

    async with aiohttp.ClientSession() as session:
        # 发现所有站点地图URL
        sitemap_urls = await fetch_sitemap_urls(target_url, session, semaphore)
        print(f"\n发现的站点地图URL:")
        for url in sitemap_urls:
            print(f"- {url}")

        # 解析每个站点地图的内容
        print(f"\n解析站点地图内容:")
        for url in sitemap_urls:
            page_urls = await parse_sitemap_content(url, session)
            for page in page_urls[:5]:  # 仅展示前5条示例
                print(f"  - {page}")

if __name__ == "__main__":
    asyncio.run(main())

依赖安装

pip install aiohttp python-robots

三、开源工具与库推荐

  • python-robots:专业的robots.txt解析库,比手动解析更可靠,支持站点地图提取
  • sitemap-parser:专门解析各种格式站点地图的库,自动处理压缩和索引型地图
  • Scrapy + scrapy-sitemap:适合大规模爬取场景,scrapy-sitemap插件可自动发现并解析站点地图
  • WordPress专属:可直接调用WP REST API(/wp-json/wp/v2/sitemaps)获取官方站点地图,或使用上述代码中的专属路径

四、合规爬取与速率控制建议

  1. 严格遵守robots.txt:即使站点地图未在robots.txt中列出,也不得爬取被Disallow的目录下的内容
  2. 速率与并发控制:
    • 单域名请求间隔至少1秒,并发数不超过5(根据服务器响应调整)
    • 用异步信号量(如代码中的Semaphore)限制并发数
  3. 伦理规范:
    • 避免在业务高峰时段(工作日9-18点)爬取商业站点
    • 不爬取需要身份验证的站点地图,除非获得站点所有者明确授权
    • 保留爬取日志(请求时间、URL、响应状态),以备合规检查
  4. 错误处理:设置合理的超时时间(10-15秒),捕获并处理网络错误,避免无限重试

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.07 01:14:50