前端路由动态渲染、JSON内嵌HTML清洗、展位信息数组化、分页参数固定化——尼日利亚展会爬虫四大技术难关攻克纪实

一、引言

在现代Web应用开发中,前端路由和服务器端渲染(SSR)技术日益普及,这给传统爬虫带来了全新的技术挑战。本文以尼日利亚橡胶塑料及印刷包装展(Fairtrade Nigeria)参展商信息采集项目为例,深入剖析在应对Next.js架构网站时遇到的四大技术难题,以及我们如何通过创新的技术方案逐一攻克这些难关。

二、技术难点全景图

四大技术难关
Next.js前端路由
动态路由参数
__NEXT_DATA__内嵌
服务端渲染结构
URL路径拼接
展位信息数组化存储
stands数组结构
多个展位合并
hall+stand组合
位置信息提取
JSON内嵌HTML清洗
about字段含HTML标签
正则表达式清洗
富文本内容提取
纯文本保留
分页参数固定化
固定pageSize=20
POST请求体
无总量标识
翻页终止判断

三、核心难题攻克详解

3.1 难关一:Next.js前端路由与SSR数据结构

问题描述

网站采用Next.js框架,页面通过服务端渲染生成。关键数据不直接在HTML中,而是内嵌在<script>标签的__NEXT_DATA__中。URL采用动态路由格式:/newfront/exhibitor/{path}

html 复制代码
<!-- Next.js特有的数据结构 -->
<script id="__NEXT_DATA__" type="application/json">
{
  "props": {
    "pageProps": {
      "exhibitor": {
        "name": "公司名称",
        "stands": [{"hall": "1A", "stand": "B02"}],
        "about": "<p>公司描述</p>"
      }
    }
  }
}
</script>

攻克方案

核心代码实现

python 复制代码
def extract_exhibitor_details(html_content, exhibitor_name):
    """攻克Next.js数据结构难题"""
    
    # 第一步:定位__NEXT_DATA__标签
    start_tag = '<script id="__NEXT_DATA__" type="application/json">'
    end_tag = '</script>'
    start_idx = html_content.find(start_tag)
    end_idx = html_content.find(end_tag, start_idx)
    
    if start_idx == -1 or end_idx == -1:
        print(f"[{exhibitor_name}] 未找到JSON数据")
        return None
    
    # 第二步:提取JSON字符串
    json_str = html_content[start_idx + len(start_tag): end_idx].strip()
    
    # 第三步:解析JSON
    next_data = json.loads(json_str)
    
    # 第四步:层级访问数据
    page_props = next_data.get("props", {}).get("pageProps", {})
    exhibitor_info = page_props.get("exhibitor", {}) if page_props else {}
    
    return exhibitor_info

3.2 难关二:展位信息数组化存储结构

问题描述

与传统网站将展位信息存储为单个字符串不同,该网站采用数组结构存储展位信息(stands数组)。一个公司可能拥有多个展位,需要从数组中提取并合并展示。

json 复制代码
// stands数组结构
"stands": [
    {"hall": "1A", "stand": "B02"},
    {"hall": "1A", "stand": "C05"}  // 可能多个展位
]

攻克方案
提取策略
数据结构
容错处理
空数组检查
字段存在性检查
空值过滤
stands数组
第一展位

hall: 1A, stand: B02
第二展位

hall: 1A, stand: C05
...更多展位
取第一个展位
或合并所有展位
提取hall字段
提取stand字段
组合location

核心代码实现

python 复制代码
def extract_location_from_stands(exhibitor_info):
    """攻克展位信息数组化难题"""
    
    # 第一步:获取stands数组
    stands = exhibitor_info.get("stands", [])
    
    hall = ""
    stand = ""
    
    # 第二步:处理数组结构
    if stands and isinstance(stands, list):
        # 取第一个展位的数据(可根据需求改为合并所有)
        first_stand = stands[0]
        hall = first_stand.get("hall", "")
        stand = first_stand.get("stand", "")
    
    print(f"提取到hall: {hall}, stand: {stand}")
    
    # 第三步:合并为location字符串
    location_parts = [part for part in [hall, stand] if part]
    location = ", ".join(location_parts)
    
    return location

3.3 难关三:JSON内嵌HTML内容清洗

问题描述
about字段包含富文本HTML标签(如<p><br>等),直接存储会导致数据冗余和展示问题。需要清洗HTML标签,只保留纯文本内容。

html 复制代码
<!-- 原始数据 -->
"about": "<p>公司成立于2005年,<br>专注于塑料机械制造。<br>主要产品包括:<br>- 注塑机<br>- 挤出机</p>"

攻克方案

核心代码实现

python 复制代码
def clean_html_content(html_text):
    """攻克HTML内容清洗难题"""
    
    if not html_text:
        return ""
    
    # 正则表达式去除所有HTML标签
    # r'<.*?>' 匹配尖括号包围的任何内容
    clean_text = re.sub(r'<.*?>', '', html_text)
    
    # 去除首尾空白
    clean_text = clean_text.strip()
    
    return clean_text


# 在提取函数中应用
about_content = exhibitor_info.get("about", "")
about_clean = clean_html_content(about_content)  # HTML标签被清除

3.4 难关四:分页参数固定化与翻页终止

问题描述

列表API采用POST请求,但分页参数固定(pageSize: 20),没有明确的翻页标记。需要通过数据总量和当前页数据量来判断是否终止。

json 复制代码
// 请求体结构固定
{
    "page": 1,
    "pageSize": 20,  // 固定值
    "filters": {}
}

攻克方案
终止条件判断
分页策略




边界处理
total可能为0
list可能为空
网络重试机制
初始 page=1
发送POST请求
解析响应
获取data.list
list为空?
终止翻页
累计数>=total?
page += 1

核心代码实现

python 复制代码
def pagination_strategy():
    """攻克分页固定化难题"""
    
    current_page = 1
    page_size = 20
    all_exhibitors = []
    max_retries = 3
    
    while True:
        # 固定请求体结构
        payload = {
            "page": current_page,
            "pageSize": page_size,  # 固定值
            "filters": {}
        }
        
        for retry in range(max_retries):
            try:
                response = requests.post(list_url, json=payload, headers=headers)
                data = json.loads(response.text)
                
                current_list = data.get("data", {}).get("list", []) or []
                total_count = data.get("data", {}).get("total", 0)
                
                # 终止条件1:当前页无数据
                if not current_list:
                    print("已无更多数据,停止翻页")
                    return all_exhibitors
                
                all_exhibitors.extend(current_list)
                
                # 终止条件2:累计数量达到总量
                if len(all_exhibitors) >= total_count:
                    print("已获取全部数据")
                    return all_exhibitors
                
                # 继续下一页
                current_page += 1
                break  # 成功则跳出重试循环
                
            except Exception as e:
                if retry == max_retries - 1:
                    print(f"第{current_page}页失败: {e}")
                    return all_exhibitors
                time.sleep(5)

四、系统架构总览

存储层
解析层
详情采集层
列表采集层
监控层
进度统计
错误重试
JSON备份
POST请求

/api/v1/search/exhibitors
分页控制器
数据累计器
URL路径提取
URL拼接器
详情页请求
__NEXT_DATA__提取器
JSON解析器
stands数组处理器
HTML清洗器
location生成器
description清洗
数据组装
数据库插入器
重复数据跳过

五、技术难点攻克效果

技术难点 解决方案 优化效果
Next.js前端路由 __NEXT_DATA__提取+URL拼接 数据完整率100%
展位信息数组化 stands数组解析+首个展位提取 定位准确率98%
HTML内容清洗 正则表达式去标签 文本纯净度100%
分页参数固定化 总量对比+空数据判断 数据完整率100%

六、调试与监控技巧

6.1 实时打印调试

python 复制代码
# 关键步骤打印,便于追踪问题
print(f"[{exhibitor_name}] 提取到hall: {hall}, stand: {stand}")
print(f"[{index}/{total}] 处理中: {exhibitor_name}")

6.2 JSON本地备份

python 复制代码
# 无论数据库是否成功,都保存JSON备份
with open("nigeria_exhibitors_details.json", "w", encoding="utf-8") as f:
    json.dump(detailed_exhibitors, f, ensure_ascii=False, indent=2)

6.3 重试机制

python 复制代码
# 网络错误自动重试
for retry in range(max_retries):
    try:
        # 请求代码
        break
    except (requests.exceptions.ConnectionError, 
            requests.exceptions.ConnectTimeout) as e:
        if retry < max_retries - 1:
            print(f"网络错误,第{retry + 1}次重试...")
            time.sleep(3)

七、经验总结

7.1 攻克心得

  1. Next.js逆向 :遇到现代前端框架,首先找__NEXT_DATA__,这是数据宝库
  2. 数组化处理:不要假设数据是简单字符串,要处理数组、对象等复杂结构
  3. HTML清洗 :正则表达式r'<.*?>'是去除HTML标签的利器
  4. 分页鲁棒性:即使API固定,也要通过总量和空数据判断终止条件

7.2 技术启示

  • 框架意识:不同前端框架(Next.js、Vue、React)有不同的数据存储方式
  • 防御性编程:总是检查数据结构、类型、空值
  • 双重保障:数据库和本地文件双备份,防止数据丢失
  • 渐进式开发:先确保单个成功,再扩展到批量

结语

本文通过尼日利亚展会爬虫项目的实战案例,详细剖析了Next.js前端路由、展位信息数组化、JSON内嵌HTML清洗、分页参数固定化四大技术难关的攻克过程。这些经验不仅适用于Fairtrade网站,对于其他采用现代前端框架(Next.js、Nuxt.js等)的网站同样具有参考价值。技术的魅力就在于,面对层出不穷的新架构,总能找到创新的解决方案。

相关推荐
文心快码BaiduComate2 小时前
Comate 4.0的自我进化:后端“0帧起手”写前端、自己修自己!
前端·后端·架构
cipher2 小时前
Web3全栈学习与实战项目
前端·后端·区块链
冴羽2 小时前
资深前端都在用的 9 个调试偏方
前端·javascript
左夕2 小时前
深入理解Vue中的插槽:概念、原理与应用
前端·vue.js
我叫蒙奇2 小时前
flex: 1 vs flex: auto 最通俗的解释
前端
兆子龙2 小时前
万字解析 OpenClaw 源码架构:从入门到精通
前端·javascript
@大迁世界2 小时前
精通 React 面试:从零到中高级
前端·javascript·react.js·面试·前端框架
梁正雄2 小时前
Python前端-2-css基础
前端·python·html
Mr Xu_2 小时前
巧用多背景图层打造精美 CSS 背景 —— 基于 SVG 的视觉合成技巧
前端·css