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

使用Scrapy爬取IMDB Top250时提取用户投票数的问题

解决Scrapy爬取IMDB Top250电影时提取用户投票数的问题

问题描述

我用Scrapy爬取IMDB Top250电影页面,目标是提取用户投票信息。对应的HTML文本节点结构如下:

" ("
"3.8M"
")"

用CSS选择器response.css('.ipc-rating-star--voteCount::text').getall()提取后,得到的是包含左括号、投票值、右括号的扁平列表,但我只想提取纯数值部分(比如3.8M),现有代码的提取方法未生效,结果不符合预期。完整代码如下:

movie_data = []

class IMDBSpider(scrapy.Spider):
    name = 'imdb_spider'

    def start_requests(self):
        headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36'}
        yield scrapy.Request(url, headers=headers, callback=self.parse)
            
    def parse(self, response):
        # movie name
        movie_name = response.css('ul.ipc-metadata-list h3.ipc-title__text::text').getall()

        # movie year
        movie_year = response.css('.sc-c7e5f54-8.hgjcbi.cli-title-metadata-item:first-child::text').getall()

        # movie ratings
        movie_rating = response.css('ul.ipc-metadata-list span.ipc-rating-star--base::text').getall()

        # user votings
        user_vote = response.css('.ipc-rating-star--voteCount::text').getall()

        print(response.css('.ipc-rating-star--voteCount::text').getall())

        for name, year, rating, votes in zip(movie_name, movie_year, movie_rating, user_vote):
            self.log(f'Processing: {name}, {year}, {rating}, {votes}')
            relevant_elements = self.extract_combined_str(votes)
            self.log(f'Relevant Elements: {relevant_elements}')
            movie_dict = {
            'movie_name': self.extract_name(name),  # Call the extract_name method using self
            'movie_year': year.strip(),
            'movie_rating': rating.strip(),
            'user_votes': [vote for vote in votes]
            }
            movie_data.append(movie_dict)


        delay = random.uniform(2, 5)
        self.log(f'Delaying for {delay} seconds.')
        time.sleep(delay)

    def extract_name(self, name):
        name = name.strip()
        name = re.sub(r'^\d+\.\s*', '', name)
        return name
    
    def extract_combined_str(self, votes):
        # Filtra solo los elementos que contienen dígitos y paréntesis
        numeric_value = re.search(r'\(([\d.]+[MK]?)\)', votes)
        return numeric_value.group(1) if numeric_value else None

问题根源

  1. 数据配对错误:user_vote是一个扁平列表,每部电影的投票信息被拆成3个独立元素(左括号、数值、右括号),但直接用zip将其与长度为250的电影名称等列表配对,导致每个votes仅拿到单个字符/元素,而非完整的投票信息组。
  2. 正则匹配无效:extract_combined_str方法试图从单个字符串中匹配括号内容,但传入的参数并非完整的带括号字符串,自然无法匹配到结果。

修正方案

步骤1:分组投票信息

将user_vote按每3个元素为一组拆分,每组对应一部电影的投票相关文本。

步骤2:提取纯数值

每组的第二个元素就是目标数值(比如3.8M),直接提取即可;若需兼容结构变化,也可通过正则从拼接后的完整字符串中提取。

修正后的完整代码

import scrapy
import random
import time
import re

movie_data = []

class IMDBSpider(scrapy.Spider):
    name = 'imdb_spider'
    start_urls = ['https://www.imdb.com/chart/top']

    def start_requests(self):
        headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36'}
        for url in self.start_urls:
            yield scrapy.Request(url, headers=headers, callback=self.parse)
            
    def parse(self, response):
        # 提取电影名称
        movie_name = response.css('ul.ipc-metadata-list h3.ipc-title__text::text').getall()

        # 提取上映年份
        movie_year = response.css('.sc-c7e5f54-8.hgjcbi.cli-title-metadata-item:first-child::text').getall()

        # 提取评分
        movie_rating = response.css('ul.ipc-metadata-list span.ipc-rating-star--base::text').getall()

        # 处理投票信息:分组后提取数值
        user_vote_raw = response.css('.ipc-rating-star--voteCount::text').getall()
        # 每3个元素为一组,对应一部电影的投票信息
        user_vote_groups = [user_vote_raw[i:i+3] for i in range(0, len(user_vote_raw), 3)]
        # 直接取每组的第二个元素作为纯投票数值
        user_votes = [group[1].strip() for group in user_vote_groups]

        for name, year, rating, votes in zip(movie_name, movie_year, movie_rating, user_votes):
            self.log(f'Processing: {name}, {year}, {rating}, {votes}')
            movie_dict = {
                'movie_name': self.extract_name(name),
                'movie_year': year.strip(),
                'movie_rating': rating.strip(),
                'user_votes': votes
            }
            movie_data.append(movie_dict)

        delay = random.uniform(2, 5)
        self.log(f'Delaying for {delay} seconds.')
        time.sleep(delay)

    def extract_name(self, name):
        name = name.strip()
        name = re.sub(r'^\d+\.\s*', '', name)
        return name

兼容结构变化的备选方案

若担心IMDB页面结构调整,可改用正则从拼接后的完整字符串中提取数值,替换上述代码中的user_votes生成逻辑:

user_votes = []
for group in user_vote_groups:
    full_vote_str = ''.join(group)
    match = re.search(r'\(([\d.]+[MK]?)\)', full_vote_str)
    user_votes.append(match.group(1) if match else None)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 19:47:40