pan11123 2025-05-21 17:12 采纳率: 0%
浏览 9

豆瓣电影爬虫问题,为什么概率爬取到imdb_id

我想爬取豆瓣电影的imdb_id,但是它是有概率爬取不到的,无论是用css定位,xpath定位还是用正则匹配,都有很大的概率获取不到

import requests
from bs4 import BeautifulSoup
import pandas as pd
import time
import random
import re
import os
from fake_useragent import UserAgent

class DoubanMovieScraper:
    def __init__(self):
        # 创建User-Agent生成器
        try:
            self.ua = UserAgent()
        except:
            # 如果无法使用fake_useragent库,使用预定义的User-Agent列表
            self.user_agents = [
                'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
                'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36',
                'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.1 Safari/605.1.15',
                'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:90.0) Gecko/20100101 Firefox/90.0',
                'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.164 Safari/537.36 Edg/91.0.864.71'
            ] 

        # 创建存放数据目录
        self.data_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'data')
        if not os.path.exists(self.data_dir):
            os.makedirs(self.data_dir)
            
        # 创建会话,维持Cookie
        self.session = requests.Session()
    
    def get_random_ua(self):
        """获取随机User-Agent"""
        try:
            return self.ua.random
        except:
            return random.choice(self.user_agents)
    
    def get_headers(self, referer=None):
        """生成请求头"""
        headers = {
            'User-Agent': self.get_random_ua(),
            'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
            'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
        }
        if referer:
            headers['Referer'] = referer
        return headers
    
    def make_request(self, url, referer=None, max_retries=3):
        """发送请求,包含重试和错误处理"""
        headers = self.get_headers(referer)
        
        for attempt in range(max_retries):
            try:
                response = self.session.get(
                    url, 
                    headers=headers, 
                    timeout=15,
                )
                
                # 检查响应状态
                if response.status_code == 200:
                    return response
                elif response.status_code == 403 or response.status_code == 429:
                    print(f"请求被拒绝(状态码:{response.status_code}),可能是被反爬措施拦截,等待更长时间...")
                    # 遇到拒绝访问,等待更长时间
                    time.sleep(random.uniform(20, 30))
                else:
                    print(f"请求失败,状态码: {response.status_code},尝试重试...")
                    
                # 如果不是最后一次尝试,等待一段时间再重试
                if attempt < max_retries - 1:
                    time.sleep(random.uniform(5, 10))
                    
            except Exception as e:
                print(f"请求发生异常: {str(e)}")
                if attempt < max_retries - 1:
                    time.sleep(random.uniform(5, 10))
        
        return None
    
    def get_top250_movies(self):
        movies = []
        base_url = 'https://movie.douban.com/top250'
        

        for start in range(0, 25, 25):
            url = f'{base_url}?start={start}'
            print(f"正在爬取: {url}")
            
            try:
                # 获取列表页
                response = self.make_request(url, referer='https://movie.douban.com/')
                
                if not response:
                    print(f"无法获取页面: {url},跳过该页")
                    continue
                
                soup = BeautifulSoup(response.text, 'html.parser')

                movie_items = soup.select('.grid_view li')
                
                if not movie_items:
                    print(f"页面未找到电影列表,可能被反爬措施拦截。页面内容: {response.text[:200]}...")
                    continue
                
                for item in movie_items:
                    try:
                        # 电影名称
                        title = item.select_one('.title').text
                        
                        # 评分
                        rating = item.select_one('.rating_num').text
                        
                        # 导演和类型信息
                        info = item.select_one('.bd p').text.strip()

                        # 提取导演信息
                        director = ""
                        if "导演:" in info:
                            director_match = re.search(r'导演:(.*?)主演:', info, re.DOTALL)
                            if director_match:
                                director = director_match.group(1).strip()
                            else:
                                # 如果没有主演信息,可能格式不同
                                director_match = re.search(r'导演:(.*?)(\d{4}|\s{2,})', info, re.DOTALL)
                                if director_match:
                                    director = director_match.group(1).strip()
                        
                        # 提取类型信息(年份/国家/类型)
                        genres = ""
                        type_match = re.search(r'\d{4}\s*/\s*([^/]*)/\s*(.*?)(?:\s{2,}|$)', info)
                        if type_match:
                            genres = type_match.group(2).strip()
                        
                        # 获取IMDB ID
                        movie_url = item.select_one('.hd a')['href']
                        print(f"正在爬取{title}的详情页: {movie_url}")
                        
                        # 尝试获取IMDb ID
                        # 在访问详情页之前额外等待,避免频繁请求
                        time.sleep(random.uniform(3, 5))
                        
                        movie_response = self.make_request(movie_url,referer=url)
                        if movie_response and movie_response.status_code == 200:
                            # 使用正则表达式直接从HTML文本中提取IMDb ID
                            imdb_id = ""
                            imdb_pattern = r'IMDb:</span>\s*<a.*?>(tt\d+)</a>'
                            imdb_match = re.search(imdb_pattern, movie_response.text)
                            
                            if imdb_match:
                                imdb_id = imdb_match.group(1)
                                print(f"已提取到IMDb ID: {imdb_id}")
                            else:
                                # 尝试另一种格式匹配
                                imdb_pattern2 = r'IMDb:</span>\s*(tt\d+)'
                                imdb_match2 = re.search(imdb_pattern2, movie_response.text)
                                if imdb_match2:
                                    imdb_id = imdb_match2.group(1)
                                    print(f"已提取到IMDb ID: {imdb_id}")
                                else:
                                    print("未找到IMDb ID")
                        else:
                            print(f"获取电影详情页失败: {movie_url}")

                        movie_info = {
                            'title': title,
                            'rating': float(rating),
                            'director': director,
                            'genres': genres,
                            'imdb_id': imdb_id,
                        }
                        
                        movies.append(movie_info)
                        print(f"已爬取电影: {title}")
                    except Exception as e:
                        print(f"处理电影项时出错: {str(e)}")
                
                # 请求间隔,避免频繁请求,增加间隔时间
                time.sleep(random.uniform(8, 15))  
            except Exception as e:
                print(f"爬取列表页出错: {str(e)}")

        
        # 保存为CSV
        df = pd.DataFrame(movies)
        csv_path = os.path.join(self.data_dir, 'douban_top250.csv')
        df.to_csv(csv_path, index=False, encoding='utf-8-sig')
        print(f"数据已保存至: {csv_path}")
        return df
    
if __name__ == "__main__":
    scraper = DoubanMovieScraper()
    scraper.get_top250_movies()

img

img

img

img

  • 写回答

4条回答 默认 最新

  • 檀越@新空间 2025-05-21 17:14
    关注

    下午好🌅🌅🌅
    本答案参考通义千问

    从你的描述来看,你希望爬取豆瓣电影的 imdb_id,但发现有很高的概率无法成功获取。以下是一些可能的原因及对应的解决方案:


    可能的原因分析

    1. 动态加载内容
      豆瓣电影页面的内容可能通过 JavaScript 动态加载,而 requests 只能获取静态 HTML,无法解析动态内容。

    2. 反爬虫机制
      豆瓣可能会对频繁请求的 IP 进行限制,导致部分请求返回空内容或错误。

    3. HTML 结构变化
      豆瓣的网页结构可能会不定期更新,CSS 选择器或 XPath 定位可能不再适用。

    4. IP 地址被封禁
      如果多次请求同一个 IP 地址,可能会触发豆瓣的反爬虫策略。

    5. 缺少必要的请求参数
      某些页面可能需要特定的 Cookie 或 Referer 头部信息才能正确加载内容。


    解决方案

    1. 使用 Selenium 或 Playwright 解决动态加载问题

    如果豆瓣的 imdb_id 是通过 JavaScript 动态加载的,可以使用 Selenium 或 Playwright 等工具模拟浏览器行为。

    修改后的代码示例(使用 Selenium):

    from selenium import webdriver
    from selenium.webdriver.chrome.service import Service
    from selenium.webdriver.common.by import By
    from selenium.webdriver.chrome.options import Options
    import time
    
    # 设置无头模式
    chrome_options = Options()
    chrome_options.add_argument("--headless")
    
    # 启动 WebDriver
    service = Service('chromedriver')  # 确保 chromedriver 在 PATH 中
    driver = webdriver.Chrome(service=service, options=chrome_options)
    
    def get_top250_movies():
        movies = []
        base_url = 'https://movie.douban.com/top250'
    
        for start in range(0, 25, 25):
            url = f'{base_url}?start={start}'
            print(f"正在爬取: {url}")
    
            driver.get(url)
            time.sleep(5)  # 等待页面完全加载
    
            # 使用 CSS 选择器提取数据
            movie_items = driver.find_elements(By.CSS_SELECTOR, '.grid_view li')
    
            for item in movie_items:
                title = item.find_element(By.CSS_SELECTOR, '.title').text
                imdb_id = item.find_element(By.CSS_SELECTOR, '.pl').text.split(': ')[-1] if ': ' in item.find_element(By.CSS_SELECTOR, '.pl').text else None
                movies.append({'title': title, 'imdb_id': imdb_id})
    
        driver.quit()
        return movies
    
    if __name__ == "__main__":
        movies = get_top250_movies()
        print(movies)
    

    注意:

    • 需要安装 selenium 和对应的浏览器驱动程序(如 chromedriver)。
    • 使用无头模式可以避免打开浏览器窗口。

    2. 增强反爬虫应对能力

    豆瓣可能会通过 IP 黑名单、请求频率限制等方式阻止爬虫。可以通过以下方式增强应对能力:

    1. 使用代理池 使用代理池轮换 IP 地址,避免因频繁请求被封禁。

      import random
      from requests.adapters import HTTPAdapter
      from requests.packages.urllib3.util.retry import Retry
      
      proxies = [
          {'http': 'http://proxy1.example.com:8080', 'https': 'http://proxy1.example.com:8080'},
          {'http': 'http://proxy2.example.com:8080', 'https': 'http://proxy2.example.com:8080'}
      ]
      
      session = requests.Session()
      retry_strategy = Retry(
          total=5,
          backoff_factor=1,
          status_forcelist=[429, 500, 502, 503, 504]
      )
      adapter = HTTPAdapter(max_retries=retry_strategy)
      session.mount("http://", adapter)
      session.mount("https://", adapter)
      
      def make_request(url):
          proxy = random.choice(proxies)
          try:
              response = session.get(url, headers=get_headers(), proxies=proxy, timeout=15)
              response.raise_for_status()
              return response
          except Exception as e:
              print(f"请求失败: {e}")
              return None
      
    2. 设置合理的请求间隔 避免短时间内发送过多请求。

      time.sleep(random.uniform(1, 3))  # 每次请求之间等待 1 到 3 秒
      

    3. 检查 HTML 结构变化

    豆瓣的网页结构可能会发生变化,建议检查目标页面的 HTML 源码,确保 CSS 选择器或 XPath 表达式仍然有效。

    例如,如果你的目标是提取 imdb_id,可以先手动检查页面源码,确认其是否存在于某个 <a> 标签中,或者是否有特定的类名。


    4. 模拟登录或伪造 Referer

    某些页面可能需要登录或特定的 Referer 头部信息才能正常加载。你可以尝试模拟登录流程或伪造 Referer。

    headers = {
        'User-Agent': self.get_random_ua(),
        'Referer': 'https://movie.douban.com/'
    }
    

    总结

    综合以上方法,推荐的解决方案如下:

    1. 使用 Selenium 或 Playwright 解决动态加载问题。
    2. 使用代理池和合理的时间间隔防止被封禁。
    3. 手动检查 HTML 结构,确保定位表达式正确。
    4. 模拟登录或伪造 Referer 头部。

    通过这些方法,你应该能够更稳定地爬取豆瓣电影的 imdb_id。如果仍有问题,请提供更多细节,例如具体的 HTML 结构或错误日志,以便进一步分析。

    评论

报告相同问题?

问题事件

  • 创建了问题 5月21日