Python中使用scrapy+splash爬取leetcode的问题

新手刚开始学习爬虫不久,现在正在尝试使用 splash 来爬取用 js 渲染的动态页面,比如从 https://leetcode.com/problemset/all/ 爬取各种题目信息。
但是在 https://leetcode.com/problems/two-sum/ 这种页面中调用 response.xpath("//div[@class=‘css-1ponsav’]")似乎并不能获取到任何信息,不知道是什么原因?
同理在 https://leetcode.com/accounts/login/ 登陆界面里试图调用 SplashFormRequest.from_response(response,…)来进行登陆操作的时候也会返回 ValueError: No <form> element found in <200 https://leetcode.com/accounts/login/>,似乎并没有抓取到表格信息?
本人不太了解前端,不知道这个跟 leetcode 用的 graphQL 有没有关系?还是因为其它原因?
Python中使用scrapy+splash爬取leetcode的问题


4 回复

为啥不直接用 graphql 接口?


我最近也在用Scrapy+Splash爬LeetCode,分享一个完整的代码示例。

首先确保环境:pip install scrapy scrapy-splash,然后启动Splash服务(Docker运行:docker run -p 8050:8050 scrapinghub/splash)。

核心代码:

import scrapy
from scrapy_splash import SplashRequest

class LeetcodeSpider(scrapy.Spider):
    name = 'leetcode'
    start_urls = ['https://leetcode.com/problemset/all/']

    def start_requests(self):
        for url in self.start_urls:
            yield SplashRequest(
                url,
                self.parse,
                args={'wait': 2, 'timeout': 90}
            )

    def parse(self, response):
        # 等待表格加载
        script = """
        function main(splash)
            splash:go(splash.args.url)
            splash:wait(2)
            return splash:html()
        end
        """
        
        yield SplashRequest(
            response.url,
            self.parse_table,
            endpoint='execute',
            args={'lua_source': script, 'timeout': 90},
            dont_filter=True
        )

    def parse_table(self, response):
        # 提取题目信息
        for row in response.css('div[role="row"]'):
            title = row.css('a[href*="/problems/"]::text').get()
            difficulty = row.css('span.difficulty-label::text').get()
            acceptance = row.css('span.text-label::text').get()
            
            if title:
                yield {
                    'title': title.strip(),
                    'difficulty': difficulty.strip() if difficulty else None,
                    'acceptance': acceptance.strip() if acceptance else None,
                    'url': response.urljoin(row.css('a[href*="/problems/"]::attr(href)').get())
                }
        
        # 处理分页
        next_page = response.css('a[aria-label="next"]::attr(href)').get()
        if next_page:
            yield SplashRequest(
                response.urljoin(next_page),
                self.parse_table,
                args={'wait': 2}
            )

settings.py配置:

SPLASH_URL = 'http://localhost:8050'

DOWNLOADER_MIDDLEWARES = {
    'scrapy_splash.SplashCookiesMiddleware': 723,
    'scrapy_splash.SplashMiddleware': 725,
    'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware': 810,
}

SPIDER_MIDDLEWARES = {
    'scrapy_splash.SplashDeduplicateArgsMiddleware': 100,
}

DUPEFILTER_CLASS = 'scrapy_splash.SplashAwareDupeFilter'

运行:scrapy crawl leetcode -o leetcode.json

关键点:

  1. 用SplashRequest替代普通Request
  2. 适当设置wait时间让JS加载
  3. LeetCode的DOM结构可能变化,需要根据实际情况调整选择器
  4. 注意频率控制,避免被封

总结:用Splash处理LeetCode的JS渲染很有效。

会不会没有渲染完你就去获取了?给个延时试试?

用接口解决了问题。。。

回到顶部