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

Scrapy+Selenium处理Nepalitimes网站Load More按钮爬取问题

解决方案:Nepalitimes网站爬取问题修复

一、Selenium触发Load More按钮失败修复

核心问题排查与调整

  • 确认按钮定位准确性:直接从浏览器开发者工具复制按钮的稳定CSS选择器/XPath(比如button.load-more-btn或//button[text()='Load More']),避免使用易变的元素路径。
  • 增加显式等待逻辑:避免页面未加载完成就执行点击操作,确保按钮处于可交互状态。
  • 循环加载直到按钮消失:通过循环触发点击,直到按钮不存在或无法点击,确保加载所有内容。

调整后代码示例

from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

def parse(self, response):
    driver = self.driver
    while True:
        try:
            # 等待Load More按钮可点击,超时10秒
            load_btn = WebDriverWait(driver, 10).until(
                EC.element_to_be_clickable((By.CSS_SELECTOR, "button[data-testid='load-more']"))
            )
            load_btn.click()
            # 等待新文章加载完成(以最后一篇文章出现为标志)
            WebDriverWait(driver, 5).until(
                EC.presence_of_element_located((By.CSS_SELECTOR, ".article-card:last-child"))
            )
        except Exception:
            # 按钮消失或无法点击,终止循环
            break
    
    # 提取所有文章数据
    articles = driver.find_elements(By.CSS_SELECTOR, ".article-card")
    for article in articles:
        yield {
            "title": article.find_element(By.CSS_SELECTOR, "h2").text,
            "url": article.find_element(By.CSS_SELECTOR, "a").get_attribute("href"),
            # 按需添加其他字段
        }

二、GraphQL接口爬取异常修复

核心问题排查与调整

  • 完善分页逻辑:通过offset和limit变量实现分页请求,每次递增offset直到返回数据为空。
  • 确保回调函数生效:发送POST请求时必须指定callback=self.parse_ajax_response,避免默认走parse函数。
  • 过滤archive页面:提取URL时判断是否包含/archive/,直接跳过此类数据。

调整后代码示例

import scrapy
import json

class NepaliTimesSpider(scrapy.Spider):
    name = "nepalitimes"
    allowed_domains = ["nepalitimes.com"]
    GRAPHQL_URL = "https://nepalitimes.com/graphql"

    def start_requests(self):
        # 初始分页参数
        offset = 0
        limit = 10
        # 从开发者工具复制的GraphQL查询语句
        query = """
        query FetchArticles($offset: Int!, $limit: Int!) {
            articles(offset: $offset, limit: $limit) {
                nodes {
                    title
                    url
                    publishDate
                }
            }
        }
        """
        payload = {
            "query": query,
            "variables": {"offset": offset, "limit": limit}
        }
        yield scrapy.Request(
            url=self.GRAPHQL_URL,
            method="POST",
            body=json.dumps(payload),
            headers={"Content-Type": "application/json"},
            callback=self.parse_ajax_response,
            meta={"offset": offset + limit, "limit": limit}
        )

    def parse_ajax_response(self, response):
        data = json.loads(response.text)
        articles = data.get("data", {}).get("articles", {}).get("nodes", [])
        
        for article in articles:
            article_url = article.get("url")
            # 过滤archive页面数据
            if "/archive/" in article_url:
                continue
            yield {
                "title": article.get("title"),
                "url": article_url,
                "publish_date": article.get("publishDate")
            }
        
        # 继续请求下一页(如果当前返回数据量等于limit,说明还有内容)
        offset = response.meta.get("offset")
        limit = response.meta.get("limit")
        if len(articles) == limit:
            query = """
            query FetchArticles($offset: Int!, $limit: Int!) {
                articles(offset: $offset, limit: $limit) {
                    nodes {
                        title
                        url
                        publishDate
                    }
                }
            }
            """
            payload = {
                "query": query,
                "variables": {"offset": offset, "limit": limit}
            }
            yield scrapy.Request(
                url=self.GRAPHQL_URL,
                method="POST",
                body=json.dumps(payload),
                headers={"Content-Type": "application/json"},
                callback=self.parse_ajax_response,
                meta={"offset": offset + limit, "limit": limit}
            )

额外注意事项

  1. 检查GraphQL接口的请求头:部分接口需要携带X-CSRF-Token等验证头,可从浏览器的Network请求中复制。
  2. 配置反爬策略:在settings.py中设置DOWNLOAD_DELAY = 2,避免请求过于频繁导致被封禁。
  3. 验证接口返回:如果返回报错,检查查询语句中的字段是否与接口定义一致,变量格式是否符合要求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 11:12:45