豆瓣网络爬虫

python实现网络爬虫

1、介绍网页编程的基本原理

网页编程是指创建和管理网站、网页和互联网应用程序的过程。它涉及使用不同的编程语言和技术来实现网页的设计、功能和交互。以下是网页编程的基本原理:

  • HTML(超文本标记语言):HTML是网页的基本构建块。它用于定义网页的结构和内容。HTML文档由各种HTML元素组成,这些元素可以定义标题、段落、图像、链接等内容。HTML使用标记(标签<>)来指定这些元素的开始和结束。

    <html>
      <head>
        <title>标题:html_test</title>
      </head>
      <body>
        <h1>欢迎访问!</h1>
        <p>~这是一个简单的网页示例~</p>
      </body>
    </html>
    ​
  • CSS(层叠样式表):CSS用于定义网页的外观和样式。通过CSS,可以选择如何呈现HTML元素,例如字体、颜色、大小、布局等。CSS使我们能够将网页内容与其外观分离,从而使样式更易于管理和修改。

    body {
      font-family: Arial, sans-serif;
      background-color: #f0f0f0;
    }
    ​
    h1 {
      color: blue;
    }
    ​
    p {
      font-size: 16px;
    }
    ​
  • JavaScript:JavaScript是一种客户端脚本语言,用于实现网页的交互和动态功能。它可以用来处理用户输入、响应事件(例如点击按钮或提交表单)、操作DOM(文档对象模型)以改变页面内容,以及与服务器通信以获取或发送数据。JavaScript可以嵌入在HTML文件中或作为外部脚本文件引用。

    function greet() {
      alert('Hello, World!');
    }
    ​
  • 服务器端编程:除了客户端编程(使用JavaScript等语言),网页编程还涉及服务器端编程。服务器端编程用于处理来自客户端的请求,通常包括数据存储和检索、用户认证、业务逻辑等。常见的服务器端编程语言包括PHP、Python、Ruby、Java和Node.js等。

  • 数据库:大多数网页应用程序需要存储和检索数据,这通常需要与数据库交互。常用的数据库系统包括MySQL、PostgreSQL、MongoDB和SQLite等。

  • API(应用程序编程接口):API允许不同的应用程序之间进行通信。在网页编程中,API通常用于与第三方服务或数据源进行交互,以获取或分享数据。RESTful API和GraphQL是常见的API标准。

2、介绍网络访问的过程

网络访问是指通过互联网访问远程服务器上的资源或服务的过程。这个过程涉及多个步骤,包括域名解析、建立连接、传输数据和关闭连接等。以下是网络访问的基本过程:

  1. URL(统一资源定位符)解析: 用户在浏览器中输入网址,浏览器解析URL,提取协议、主机名(域名)、端口号和路径等信息。

  2. DNS解析: 浏览器向本地DNS服务器发起域名解析请求,本地DNS服务器查询域名对应的IP地址并返回给浏览器。

  3. 建立TCP连接: 浏览器根据获取到的IP地址和端口号,通过TCP/IP协议向服务器发起连接请求。这个过程通常经过三次握手,确保双方建立稳定的连接。

  4. HTTP请求: 一旦建立TCP连接,浏览器向服务器发送HTTP请求,包括请求方法(GET、POST等)、请求头、请求体等。请求头中包含了客户端的一些信息,如浏览器类型、支持的压缩算法等。

  5. 服务器处理请求: 服务器接收到HTTP请求后,根据请求头和请求体中的信息,处理请求并准备响应数据。

  6. HTTP响应: 服务器将处理好的响应数据以HTTP响应的形式返回给浏览器,包括响应状态码、响应头和响应体。响应头包含了服务器的一些信息,如响应的内容类型、编码方式、缓存控制等。

  7. 接收响应: 浏览器接收到HTTP响应后,根据响应头中的信息进行处理,如解析响应体、渲染页面等。

  8. 关闭TCP连接: 通信完成后,浏览器和服务器关闭TCP连接,释放资源。

  9. 页面渲染: 浏览器根据接收到的响应数据,解析HTML、CSS和JavaScript,构建DOM树、CSSOM树和JavaScript引擎运行相应的脚本,最终渲染出用户可见的页面。

3、介绍爬虫的基本原理

爬虫(Web Crawler)是一种用于自动从互联网上收集信息的程序。爬虫的基本原理涉及以下几个关键步骤:

  1. 选择起始点: 爬虫首先需要选择一个或多个起始点,这些起始点是爬虫开始抓取信息的网页链接或URL。这些链接可以是特定网站的主页、特定主题的索引页或任何其他感兴趣的链接。

  2. 发起HTTP请求: 爬虫使用HTTP或HTTPS协议向起始点的URL发送请求,以获取网页的HTML内容。这个过程通常涉及使用HTTP库(如Python中的Requests库)来构建和发送HTTP请求。

  3. 获取响应: 一旦请求发送到服务器,服务器将返回网页的响应,包括HTTP响应头和响应体。爬虫需要解析响应以提取所需的信息。

  4. 解析HTML: 爬虫解析HTML响应,通常使用HTML解析库(如Python中的Beautiful Soup或lxml)来将HTML文档转换成可操作的数据结构,如DOM树。这使得爬虫可以轻松地浏览和操作网页内容。

  5. 提取数据: 爬虫从HTML文档中提取所需的数据。这可以包括文本、图像、链接、元数据等。提取数据的过程通常涉及使用选择器(如CSS选择器或XPath)来定位和提取特定的HTML元素。

  6. 存储数据: 爬虫将提取的数据存储在本地文件、数据库或其他数据存储系统中,以备将来使用或分析。

  7. 跟踪链接: 爬虫在抓取网页的同时会检查并提取新的链接,将它们添加到一个待访问的链接队列中。这使得爬虫可以继续遍历更多的页面,不仅限于起始点。

  8. 设置爬取规则: 爬虫通常需要遵循一定的爬取规则,以控制其行为。这些规则可以包括深度限制(爬取的层级深度)、频率限制(请求速率)、排除规则(不访问特定URL或域名)等,以避免对目标网站造成过大的负担或违反法律规定。

  9. 循环遍历: 爬虫通过不断地从待访问的链接队列中获取链接,发送HTTP请求,获取响应,解析和提取数据,以及添加新链接到队列中的方式,循环遍历不同的网页,直到满足停止条件(如达到最大爬取数量或深度)为止。

  10. 异常处理: 爬虫需要处理各种异常情况,如网络错误、链接失效、反爬虫机制等。这包括重新尝试失败的请求、绕过验证码或登录页面等策略。

爬虫的基本原理是通过模拟人工访问网页的过程,自动化地抓取和提取互联网上的信息。爬虫也需要考虑性能、并发和数据存储等方面的问题,以构建高效和可靠的爬虫系统。

4、高阶爬虫技术

高阶爬虫技术是指在爬虫应用程序中使用更复杂、更智能的技术和策略,以应对各种复杂的情况和需求。这些技术可以帮助爬虫更高效地抓取信息、绕过反爬虫措施、提高数据质量以及确保合规性。以下是一些高阶爬虫技术:

  1. 用户代理池: 网站通常会根据用户代理(User-Agent)来识别爬虫。为了避免被识别为爬虫,爬虫可以使用用户代理池,定期更换User-Agent,模拟不同类型的用户访问。

  2. IP代理池: 一些网站可能会对来自同一IP地址的频繁请求采取限制或封锁措施。使用IP代理池可以轮流使用多个IP地址,以减轻这种限制。需要谨慎使用IP代理,以避免侵犯法律或违反网站的使用政策。

  3. 验证码识别: 有些网站为了防止机器人爬取,会使用验证码。高级爬虫可以集成验证码识别技术,自动识别和解锁验证码,继续抓取数据。

  4. 登录和会话管理: 一些网站要求用户登录才能访问内容。高级爬虫可以模拟用户登录,管理会话状态,并在登录后访问受限资源。这通常涉及使用HTTP请求头中的Cookie来维护会话。

  5. 数据解析和清洗: 高级爬虫可以使用更复杂的数据解析技术,例如使用正则表达式、XPath、CSS选择器或自然语言处理(NLP)技术来提取和清洗数据,以确保数据的准确性和可用性。

  6. 分布式爬取: 为了提高爬取效率和处理大量数据,可以使用分布式爬虫架构,将任务分配给多个爬虫节点并协同工作。常见的工具包括Scrapy集成了分布式爬虫的功能。

  7. 反反爬虫策略: 网站可能会采用各种反爬虫措施,如动态加载内容、异步请求、频率限制等。高级爬虫需要实施相应的策略来绕过这些障碍,如使用无头浏览器模拟用户行为,处理JavaScript渲染等。

  8. 定时任务和调度: 爬虫可以设置定时任务和调度,以定期更新数据或监视特定网站的变化。这可以通过工具如Celery或APScheduler来实现。

  9. 数据存储和处理: 高级爬虫需要考虑数据的有效存储和处理。这可能包括使用数据库、消息队列、分布式存储系统等技术,以及数据备份和恢复策略。

  10. 合规性和伦理: 在高级爬虫开发中,合规性和伦理问题变得更加重要。开发人员需要确保他们的爬虫遵守法律法规、网站的使用政策,并尊重网站所有者的权益。

5、实验二相关项目

5.1 基础代码的注释补充

程序代码

#import pandas as pd
from urllib.request import urlopen
from bs4 import BeautifulSoup
import ssl
​
context = ssl._create_unverified_context() #表示忽略未经核实的SSL证书认证
​
all_quotes = []
​
url = f'https://quotes.toscrape.com/page/1/' # 目标网址
# 用浏览器打开网址,查看源代码
page = urlopen(url,context = context) # 请求网页信息
# 将网页信息组合为BeautifulSoup结构,利用HTML解析器
soup = BeautifulSoup(page, 'html.parser') 
# 搜寻quote标记
quotes = soup.find_all('div', class_='quote')
# 逐行注释代码:
for quote in quotes:
    # 循环每一个元素 找到每个名言的内容
    text = quote.find('span', class_='text').text 
    # 循环每一个元素 找到每个名言的作者信息
    author = quote.find('small', class_='author').text
    # 循环每一个元素 找到每个名言的标签信息 存在列表中
    tags = quote.find('div', class_='tags').find_all('a')
    # 初始化存储标签信息的列表
    tags_list = []
    # 对每个标签元素:
    for tag in tags:
        # 用append方法 把标签信息存在之前初始化的列表中
        tags_list.append(tag.text)
    # 每个引用的内容信息包括 名言文本 作者 所属标签
    single_quote = [text, author, tags_list]
    # 把所有爬取到并且整理好的信息存储在all_quotes
    all_quotes.append(single_quote)
    # 爬取结果展示
    print(all_quotes)
​

5.2 爬取quotes网站

任务要求

添加代码,爬取相同网站的10个页面内容,并将爬取内容存储在同一个CSV格式文件

程序代码
#import pandas as pd
from urllib.request import urlopen
from bs4 import BeautifulSoup
import ssl
​
context = ssl._create_unverified_context() #表示忽略未经核实的SSL证书认证
​
all_quotes = []
​
url = f'https://quotes.toscrape.com/page/1/' # 目标网址
# 用浏览器打开网址,查看源代码
page = urlopen(url,context = context) # 请求网页信息
# 将网页信息组合为BeautifulSoup结构,利用HTML解析器
soup = BeautifulSoup(page, 'html.parser') 
# 搜寻quote标记
quotes = soup.find_all('div', class_='quote')
# 逐行注释代码:
for quote in quotes:
    # 循环每一个元素 找到每个名言的内容
    text = quote.find('span', class_='text').text 
    # 循环每一个元素 找到每个名言的作者信息
    author = quote.find('small', class_='author').text
    # 循环每一个元素 找到每个名言的标签信息 存在列表中
    tags = quote.find('div', class_='tags').find_all('a')
    # 初始化存储标签信息的列表
    tags_list = []
    # 对每个标签元素:
    for tag in tags:
        # 用append方法 把标签信息存在之前初始化的列表中
        tags_list.append(tag.text)
    # 每个引用的内容信息包括 名言文本 作者 所属标签
    single_quote = [text, author, tags_list]
    # 把所有爬取到并且整理好的信息存储在all_quotes
    all_quotes.append(single_quote)
    # 爬取结果展示
    print(all_quotes)
​

5.3 模拟会员登录过程

程序代码
import time
from selenium import webdriver
from selenium.webdriver.common.by import By  # 导入By模块
​
# 打开Firefox浏览器
driver = webdriver.Firefox()
​
# 指定加载页面
driver.get("http://quotes.toscrape.com/login")
​
# 等待页面加载完成
time.sleep(2)
​
# 定位用户名和密码输入框,并输入用户名和密码
username_input = driver.find_element(By.XPATH, '//*[@id="username"]')  # 使用By.XPATH定位
password_input = driver.find_element(By.XPATH, '//*[@id="password"]')  # 使用By.XPATH定位
​
username_input.send_keys('yuanjianying')  # 用户名
password_input.send_keys('yjy')  # 密码
​
# 提交登录
login_button = driver.find_element(By.XPATH, '/html/body/div/form/input[2]')  # 使用By.XPATH定位
login_button.click()
​
# 停顿15s,可以看到登录后的效果
time.sleep(15)
​
# 关闭浏览器
driver.quit()

6、虚拟网站爬取结果

6.1 串联爬取结果与性能分析

任务要求

爬取:图片、价格、评价,书名等书籍所有内容。图片直接下载在文件夹,文字信息和图片路径保存在csv文件。

网址: All products | Books to Scrape - Sandbox

注意,本网站有20页书籍,都要爬。

程序代码
import os
import requests
from bs4 import BeautifulSoup
import csv
from urllib.parse import urljoin
import time
​
start = time.time()
# 创建目录用于存储图片
if not os.path.exists('book_images'):
    os.makedirs('book_images')
​
# 打开CSV文件以写入数据
with open('book_data.csv', 'w', newline='', encoding='utf-8') as csv_file:
    writer = csv.writer(csv_file)
    writer.writerow(['Title', 'Price', 'Rating', 'Image URL'])
​
    # 循环遍历不同页面
    base_url = 'http://books.toscrape.com/catalogue/page-{}.html'
    num_pages = 50  # 总共有50页
    for page_num in range(1, num_pages + 1):
        print(f"进入第{page_num}页")
        url = base_url.format(page_num)
        response = requests.get(url)
        soup = BeautifulSoup(response.text, 'html.parser')
​
        # 查找书籍信息
        books = soup.find_all('article', class_='product_pod')
        for book in books:
            title = book.h3.a.attrs['title']
            title = title.replace('\\', ' ').replace('/', ' ').replace(':', ' ').replace('*', ' ').replace('?', ' ').replace('"', ' ').replace('<', ' ').replace('>', ' ').replace('|', ' ')
            price = book.select('div p.price_color')[0].text
            rating = book.p.attrs['class'][1]
​
            # 获取书籍图片URL
            img_url_relative = book.find('img')['src']
            img_url = urljoin(url, img_url_relative)
​
            # 下载图片到book_images目录
            img_filename = os.path.join('book_images', f'{title.replace(":", ":")}.png')
            img_response = requests.get(img_url)
            with open(img_filename, 'wb') as img_file:
                img_file.write(img_response.content)
            print(f"第{page_num}页的图片已下载完毕")
            # 写入CSV文件
            writer.writerow([title, price, rating, img_filename])
​
​
print("=== 已完成 ===")
​
end = time.time()
run = end - start
print(f"单线程执行时间是{run}秒")
爬取结果
  • csv文件

  • 文件夹结果

6.2 高阶爬虫结果与性能分析

任务要求

爬取:图片、价格、评价,书名等书籍所有内容。

网址: All products | Books to Scrape - Sandbox

注意,本网站有20页书籍,都要爬。

测试一下技术:

(1)多进程多线程技术

(2)数据库交互(redis/mongodb/mysql)

(3)虚拟地址

程序代码
import os
import requests
from bs4 import BeautifulSoup
import csv
from urllib.parse import urljoin
import time
​
start = time.time()
# 创建目录用于存储图片
if not os.path.exists('book_images'):
    os.makedirs('book_images')
​
# 打开CSV文件以写入数据
with open('book_data.csv', 'w', newline='', encoding='utf-8') as csv_file:
    writer = csv.writer(csv_file)
    writer.writerow(['Title', 'Price', 'Rating', 'Image URL'])
​
    # 循环遍历不同页面
    base_url = 'http://books.toscrape.com/catalogue/page-{}.html'
    num_pages = 50  # 总共有50页
    for page_num in range(1, num_pages + 1):
        print(f"进入第{page_num}页")
        url = base_url.format(page_num)
        response = requests.get(url)
        soup = BeautifulSoup(response.text, 'html.parser')
​
        # 查找书籍信息
        books = soup.find_all('article', class_='product_pod')
        for book in books:
            title = book.h3.a.attrs['title']
            title = title.replace('\\', ' ').replace('/', ' ').replace(':', ' ').replace('*', ' ').replace('?', ' ').replace('"', ' ').replace('<', ' ').replace('>', ' ').replace('|', ' ')
            price = book.select('div p.price_color')[0].text
            rating = book.p.attrs['class'][1]
​
            # 获取书籍图片URL
            img_url_relative = book.find('img')['src']
            img_url = urljoin(url, img_url_relative)
​
            # 下载图片到book_images目录
            img_filename = os.path.join('book_images', f'{title.replace(":", ":")}.png')
            img_response = requests.get(img_url)
            with open(img_filename, 'wb') as img_file:
                img_file.write(img_response.content)
            print(f"第{page_num}页的图片已下载完毕")
            # 写入CSV文件
            writer.writerow([title, price, rating, img_filename])
​
​
print("=== 已完成 ===")
​
end = time.time()
run = end - start
print(f"单线程执行时间是{run}秒")
爬取结果

通过观察虚拟书店网站,我们可以发现一共是有50页,每页会有20本书籍,一共是1000本书籍;

  • 程序运行结果:

  • MySQL运行结果:

    use bookstore;
    truncate table books; // 清除之前的表内容 保留表结果
    // 等待程序运行完毕
    select * from books;

  • 本地文件夹运行结果:

性能分析

导入threading,使用多线程,一共50页的虚拟书店网站,每一页开启一个进程,全部进程结束之后再执行后面的程序。

使用ThreadPoolExecutor,允许多个图片同时下载。

一共用时259s,和网络状况有关。

可能会多测试几次运行时间:

7、豆瓣影评爬取结果

7.1 串联爬取结果与性能分析(长影评部分)

任务要求

取2022年度电影的最高评分韩国电影,但因为这一类榜单中只有5部,又加入了2022最高评分日本电影,总计10部电影,每部电影爬取10页影评,存入csv文件。

程序代码
from selenium import webdriver
from selenium.webdriver.common.by import By
from bs4 import BeautifulSoup
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import csv
import time
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.keys import Keys
​
def login_with_cookies(driver):
​
    # 添加新的Cookie
    cookies = {

    }
​
    # 访问网站
    driver.get("https://movie.douban.com/")
    for key, value in cookies.items():
        driver.add_cookie({'name': key, 'value': value})
​
​
    # 可以看到已登录
    driver.refresh()
​
​
start = time.time()
# 创建一个浏览器实例
driver = webdriver.Firefox()
login_with_cookies(driver)
​
​
​
movie_links = [
    {'url': 'https://movie.douban.com/subject/35073886/?source=2022_annual_movie', 'name': '分手的决心 헤어질 결심'},
    {'url': 'https://movie.douban.com/subject/35160926/?source=2022_annual_movie', 'name': '狩猎 헌트'},
    {'url': 'https://movie.douban.com/subject/35441582/?source=2022_annual_movie', 'name': '6/45 육사오'},
    {'url': 'https://movie.douban.com/subject/30267287/?source=2022_annual_movie', 'name': '犯罪都市2 범죄도시2'},
    {'url': 'https://movie.douban.com/subject/35743103/?source=2022_annual_movie', 'name': '小说家的电影 소설가의 영화'},
    {'url': 'https://movie.douban.com/subject/35015968/?source=2022_annual_movie', 'name': '昨日的美食 电影版'},
    {'url': 'https://movie.douban.com/subject/35597426/?source=2022_annual_movie', 'name': '稍微想起一些'},
    {'url': 'https://movie.douban.com/subject/34809360/?source=2022_annual_movie', 'name': '在街上'},
    {'url': 'https://movie.douban.com/subject/34905647/?source=2022_annual_movie', 'name': '由宇子的天平'},
    {'url': 'https://movie.douban.com/subject/35372792/?source=2022_annual_movie', 'name': '老师,您能坐在我旁边吗?'}
​
]
​
with open('Korea_movies_2022_comments.csv', 'w', newline='', encoding='utf-8') as csv_file:
    fieldnames = ['电影名称','用户名', '评论时间', '评论内容', '评分等级', '点赞数','点踩数','回复数']
    writer = csv.DictWriter(csv_file, fieldnames=fieldnames)
    writer.writeheader()
    for movie_info in movie_links:
        print("===",movie_info['name'],"===")
        # 访问电影的评论页面
        driver.get(movie_info['url'])
        # 使用相对XPath来查找"更多影评"链接
        more_comments_link = driver.find_element(By.XPATH, "//a[contains(text(), '更多影评')]")
        more_comments_link.click()
        print("从第一页开始")
​
​
        for i in range(1,11):
            # 等待影评加载完成
            WebDriverWait(driver,
                          10).until(EC.presence_of_element_located((By.CLASS_NAME, 'review-list')))
​
            print("本页评论加载完成")
            
            movie_comment_page_source = driver.page_source
​
            # 使用BeautifulSoup解析HTML源代码
            soup = BeautifulSoup(movie_comment_page_source, 'html.parser')
​
            # 找到所有的影评块
            comment_blocks = soup.find_all('div', class_='main review-item')
​
            # 遍历每个短评块并提取信息
            for comment_block in comment_blocks:
                # 用户名
                username = comment_block.find('a', class_='name').text.strip()
​
                # 评论时间
                comment_time = comment_block.find('span', class_='main-meta').text.strip()
                # 评论内容
                short_content = comment_block.find('div', id=lambda x: x and x.startswith('review_') and x.endswith('_short'))
​
                
                # 找到包含评分星级的<span>标签
                rating_span = comment_block.find('span', class_=lambda x: x and 'allstar' in x)
                if rating_span:
                    # 获取<span>标签的class属性值
                    class_value = rating_span['class']
​
                    # 从class属性值中提取评分等级
                    rating = class_value[0].replace('allstar', '')
                else:
                    rating = "未评分"
​
                # 点赞数
                upvote = comment_block.find('a', class_='action-btn up').find('span', id=lambda x: x and x.startswith('r-useful_count')).text.strip()
​
                # 反对数
                downvote = comment_block.find('a', class_='action-btn down').find('span', id=lambda x: x and x.startswith('r-useless_count')).text.strip()
​
                # 回应数
                reply_count = comment_block.find('a', class_='reply').text.strip().replace('回应', '')
​
​
                # 找到评论内容块
                comment_content_block = comment_block.find('div', class_='review-short')
                if comment_content_block:
                    comment_content = comment_content_block.text.strip()
​
                # 如果有展开按钮,点击展开以获取完整评论
                WebDriverWait(driver, 10).until(EC.element_to_be_clickable((By.XPATH, "//a[contains(text(), '展开')]")))
​
                expand_button = WebDriverWait(driver, 10).until(EC.element_to_be_clickable((By.XPATH, "//a[contains(text(), '展开')]")))
                driver.execute_script("arguments[0].scrollIntoView();", expand_button)
                expand_button.click()
                time.sleep(1)  # 等待评论加载
                
                # 获取展开后的页面源代码
                movie_comment_page_source_after_expand = driver.page_source
                soup_after_expand = BeautifulSoup(movie_comment_page_source_after_expand, 'html.parser')
​
                # 获取展开后的评论内容
                full_comment_block = soup_after_expand.find('div', {'data-author': username})
                if full_comment_block:
                    full_comment = full_comment_block.text.strip()
                    comment_content = full_comment
​
                # 将换行符替换为空格
                comment_content = comment_content.replace('\n', ' ')
​
​
                # 将信息写入CSV文件
                writer.writerow({'电影名称': movie_info['name'],'用户名': username, '评论时间': comment_time, '评论内容': comment_content, '评分等级': rating, '点赞数': upvote,'点踩数':downvote,'回复数':reply_count})
​
            print(f"==={movie_info['name']}的第{i}页已经爬取完毕===")
​
            # 寻找下一页短评的元素,并等待它可点击
            next_page = WebDriverWait(driver, 30).until(
                EC.element_to_be_clickable((By.CLASS_NAME, "next"))
            )
            # 滚动到下一页元素位置
            driver.execute_script("arguments[0].scrollIntoView();", next_page)
            time.sleep(1)            
            # 然后再点击
            next_page.click()
end = time.time()
run = end - start
print(f"===全部完成,用时{run}s===")
爬取结果

以下是结果展示

性能分析

用时是120.92306709289551秒(

7.2 高阶爬虫结果与性能分析(长影评部分)

任务要求

取2022年度电影的最高评分韩国电影,但因为这一类榜单中只有5部,又加入了2022最高评分日本电影,总计10部电影,每部电影爬取10页影评,存入csv文件。并加入以下技术:

(1)多进程多线程技术

(2)数据库交互( redis/mongodb/mysql )

(3)虚拟地址

可能遇到困难:反爬虫机制

程序代码
create database Douban character set utf8;
use douban;
select * from comments;
truncate comments; // 用于测试之前清楚表内数据
ALTER TABLE comments CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
​
from selenium import webdriver
from selenium.webdriver.common.by import By
from bs4 import BeautifulSoup
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException
import random
import time
import pymysql
import multiprocessing
import re
from selenium.webdriver.support.ui import WebDriverWait
​
​
def remove_emoji(text):
    # 使用正则表达式移除表情符号
    emoji_pattern = re.compile("["
                               u"\U0001F600-\U0001F64F"  # emoticons
                               u"\U0001F300-\U0001F5FF"  # symbols & pictographs
                               u"\U0001F680-\U0001F6FF"  # transport & map symbols
                               u"\U0001F700-\U0001F77F"  # alchemical symbols
                               u"\U0001F780-\U0001F7FF"  # Geometric Shapes Extended
                               u"\U0001F800-\U0001F8FF"  # Supplemental Arrows-C
                               u"\U0001F900-\U0001F9FF"  # Supplemental Symbols and Pictographs
                               u"\U0001FA00-\U0001FA6F"  # Chess Symbols
                               u"\U0001FA70-\U0001FAFF"  # Symbols and Pictographs Extended-A
                               u"\U00002702-\U000027B0"
                               u"\U000024C2-\U0001F251"
                               "]+", flags=re.UNICODE)
    return emoji_pattern.sub(r'', text)
​
def ip_random():
    #  developer tools -> Network -> Header -> Response header [raw]
    # 修改为自己浏览器的headers
    # Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/117.0
    headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/117.0'}
    '''
    前版本代码
    API_link = 'https://api.hailiangip.com:8522/api/getIpEncrypt?dataType=1&encryptParam=1Pofnwjjlmawt1HAVLj6tTxUJo8Ctj43ciCg9jb8LL9ZnRSnD61wqH3N2dPDshZ9%2B3kkTAQZ2MD%2FIbJREwKEhHndyjBrABrC6w9Kgqc82yZyTTUse5IUp%2B%2Bl1F%2FVhbLSlhmBtWyY09Uxpbc64mJ2a3wBfYbfJtr8aD2UGympj%2FHBd1HPaJFym4rBQnOJ7ZU9arf5KKS7AFD8J5Z1OJJ%2FWbhiKf6bPqRShUoN9E1%2FgqE%3D'
    response = requests.get(url=API_link, headers=headers)
    # IP地址拆分
    IP_list = response.text.split("\n")
    proxy_list = []
    '''
    IP_list = [
        "114.232.109.213",
        "113.124.216.126",
        "60.170.204.30",
        "183.236.232.160",
        "182.34.101.96"
    ]
    proxy_list = []
    # prefix = 'https:' 前版本代码
    prefix = 'http:'
    for ip in IP_list:
        if ip != '':
            # 添加网页前缀
            proxy_list.append(prefix + ip)
    proxy = random.choice(proxy_list)
    return proxy
​
def login_with_cookies(driver):
    # 获取代理IP
    proxy = ip_random()
    print("使用代理IP:", proxy)
    # 设置代理
    webdriver.DesiredCapabilities.FIREFOX['proxy'] = {
        "httpProxy": proxy,
        "ftpProxy": proxy,
        "sslProxy": proxy,
        "proxyType": "MANUAL"
    }
​
    # 访问网站
    driver.get("https://movie.douban.com/")
​
    # 添加新的Cookie
    cookies = {

​
    }
​
    for key, value in cookies.items():
        driver.add_cookie({'name': key, 'value': value})
​
    # 可以看到已登录
    driver.refresh()
​
# 链接 MySQL
conn = pymysql.connect(
    host='localhost',
    user='root',
    password='20031008',
    database='Douban',
    charset='utf8mb4'
)
# 创建游标
cursor = conn.cursor()
​
​
# 将数据插入数据库
def insert_data_to_mysql(movie_info, username, comment_time, comment_content, rating, upvote,downvote, reply_count):
    try:
        # SQL插入语句
        sql = "INSERT INTO comments (movie_name, username, comment_time, comment_content, rating, upvote,downvote, reply_count) VALUES (%s, %s, %s, %s, %s, %s,%s,%s)"
        # 执行SQL插入操作
        cursor.execute(sql, (movie_info, username, comment_time, comment_content, rating, upvote,downvote, reply_count))
        # 提交更改
        conn.commit()
    except Exception as e:
        # 如果出现错误,回滚更改
        print(f"插入数据时出错: {str(e)}")
        print(username)
        conn.rollback()
​
# 创建代理IP池
proxy_pool = []
​
def init_proxy_pool():
    for _ in range(10):  # 创建10个代理IP
        proxy = ip_random()
        proxy_pool.append(proxy)
​
def get_next_proxy():
    if len(proxy_pool) == 0:
        init_proxy_pool()  # 如果代理池为空,重新初始化
    return proxy_pool.pop()  # 获取下一个代理IP,并从池中移除
​
def scrape_and_store(movie_info):
    # 创建一个浏览器实例
    driver = webdriver.Firefox()
    login_with_cookies(driver)
    try:
        # 获取代理IP
        proxy = get_next_proxy()
        print("使用代理IP:", proxy)
​
        # 设置代理
        webdriver.DesiredCapabilities.FIREFOX['proxy'] = {
            "httpProxy": proxy,
            "ftpProxy": proxy,
            "sslProxy": proxy,
            "proxyType": "MANUAL"
        }
​
        # 获取电影评论页面的URL
        movie_url = movie_info['url']
        movie_name = movie_info['name']
        print("===", movie_name, "===")
        
        # 访问电影的评论页面
        driver.get(movie_url)
        driver.set_window_size(1920, 1080)  # 根据需要设置窗口大小
        # 使用相对XPath来查找
        more_comments_link = WebDriverWait(driver, 30).until(
        EC.presence_of_element_located((By.XPATH, "//a[contains(text(), '更多剧评')]"))
        )
        more_comments_link.click()
        print("从第一页开始")
​
        for i in range(1,11):
            # 等待影评加载完成
            WebDriverWait(driver,
                          10).until(EC.presence_of_element_located((By.CLASS_NAME, 'review-list')))
​
            print("本页评论加载完成")
            
            movie_comment_page_source = driver.page_source
​
            # 使用BeautifulSoup解析HTML源代码
            soup = BeautifulSoup(movie_comment_page_source, 'html.parser')
​
            # 找到所有的影评块
            comment_blocks = soup.find_all('div', class_='main review-item')
​
            # 遍历每个短评块并提取信息
            for comment_block in comment_blocks:
                # 用户名
                username = comment_block.find('a', class_='name').text.strip()
​
                # 评论时间
                comment_time = comment_block.find('span', class_='main-meta').text.strip()
                # 评论内容
                short_content = comment_block.find('div', id=lambda x: x and x.startswith('review_') and x.endswith('_short'))
​
                
                # 找到包含评分星级的<span>标签
                rating_span = comment_block.find('span', class_=lambda x: x and 'allstar' in x)
                if rating_span:
                    # 获取<span>标签的class属性值
                    class_value = rating_span['class']
​
                    # 从class属性值中提取评分等级
                    rating = class_value[0].replace('allstar', '')
                else:
                    rating = "未评分"
​
                # 点赞数
                upvote_span = comment_block.find('a', class_='action-btn up').find('span', id=lambda x: x and x.startswith('r-useful_count'))
                try:
                    upvote = int(upvote_span.text.strip()) if upvote_span.text.strip() else 0
                except ValueError:
                    upvote = 0  
​
                # 反对数
                downvote_span = comment_block.find('a', class_='action-btn down').find('span', id=lambda x: x and x.startswith('r-useless_count'))
                try:
                    downvote = int(downvote_span.text.strip()) if downvote_span.text.strip() else 0
                except ValueError:
                    downvote = 0  
                
​
                # 回应数
                reply_count = comment_block.find('a', class_='reply').text.strip().replace('回应', '')
​
​
                # 找到评论内容块
                comment_content_block = comment_block.find('div', class_='review-short')
                if comment_content_block:
                    comment_content = comment_content_block.text.strip()
                try:
                    # 如果有展开按钮,点击展开以获取完整评论
                    WebDriverWait(driver, 10).until(EC.element_to_be_clickable((By.XPATH, "//a[contains(text(), '展开')]")))
​
                    expand_button = WebDriverWait(driver, 10).until(EC.element_to_be_clickable((By.XPATH, "//a[contains(text(), '展开')]")))
                    driver.execute_script("arguments[0].scrollIntoView();", expand_button)
                    expand_button.click()
                    time.sleep(0.3)  # 等待评论加载
                    
                    # 获取展开后的页面源代码
                    movie_comment_page_source_after_expand = driver.page_source
                    soup_after_expand = BeautifulSoup(movie_comment_page_source_after_expand, 'html.parser')
​
                    # 获取展开后的评论内容
                    full_comment_block = soup_after_expand.find('div', {'data-author': username})
                    if full_comment_block:
                        full_comment = full_comment_block.text.strip()
                        comment_content = full_comment
                except TimeoutException:
                    # 在超时后处理,例如记录一条消息
                    print("未找到展开按钮,跳过展开评论")
                # 将换行符替换为空格
                comment_content = comment_content.replace('\n', ' ')
                # 处理包含特殊字符的字符串
                username = username.encode('utf-8', 'ignore').decode('utf-8')
                remove_emoji(username)
                comment_content = comment_content.encode('utf-8', 'ignore').decode('utf-8')
                remove_emoji(comment_content)
                # 写入MySQL
                insert_data_to_mysql(movie_name, username, comment_time, comment_content, rating, upvote, downvote, reply_count)
​
            print(f"{movie_name}的第{i}页已经爬取完毕")
​
            # 寻找下一页短评的元素,并等待它可点击
            next_page = WebDriverWait(driver, 30).until(
                EC.element_to_be_clickable((By.CLASS_NAME, "next"))
            )
            # 滚动到下一页元素位置
            driver.execute_script("arguments[0].scrollIntoView();", next_page)
​
            # 等待遮挡元素消失
            wait = WebDriverWait(driver, 10)
            wait.until(EC.invisibility_of_element_located((By.CSS_SELECTOR, 'div.action.fixed-action')))
​
            # 等待下一页按钮可点击
            next_page = WebDriverWait(driver, 10).until(EC.element_to_be_clickable((By.CLASS_NAME, "next")))
            next_page.click()
            # next_page = driver.find_element(By.CLASS_NAME, "next")
            # driver.execute_script("arguments[0].click();", next_page)
​
​
    except Exception as e:
        print(f"爬取电影评论时出错: {str(e)}")
    finally:
        # 关闭浏览器
        driver.quit()
        # 将代理IP放回池中,以便下次使用
        proxy_pool.append(proxy)
​
​
if __name__ == "__main__":
    
    start = time.time()
    # 创建多进程池
    pool = multiprocessing.Pool(processes=11)
​
​
    # 2022年度榜单
    movie_links = [
    {'url': 'https://movie.douban.com/subject/35314632/', 'name': '黑暗荣耀'},
    {'url': 'https://movie.douban.com/subject/35322421/?source=2022_annual_movie', 'name': '我的解放日志'},
    {'url': 'https://movie.douban.com/subject/35524446/?source=2022_annual_movie', 'name': '非常律师禹英禑'},
    {'url': 'https://movie.douban.com/subject/35248792/?source=2022_annual_movie', 'name': '少年法庭'},
    {'url': 'https://movie.douban.com/subject/30291070/?source=2022_annual_movie', 'name': '财阀家的小儿子'},
    
    {'url': 'https://movie.douban.com/subject/26816519/', 'name': '逃避虽可耻但有用'},
    {'url': 'https://movie.douban.com/subject/26921674/', 'name': '东京女子图鉴'},
    {'url': 'https://movie.douban.com/subject/27140017/', 'name': '非自然死亡'},
    {'url': 'https://movie.douban.com/subject/36156235/', 'name': '重启人生'},
    {'url': 'https://movie.douban.com/subject/24321344/', 'name': '面包和汤和猫咪好天气'}    ]
​
​
    # 将每个电影链接分配给不同的进程
    pool.map(scrape_and_store, movie_links)
​
​
    # 关闭游标和数据库连接
    cursor.close()
    conn.close()
    # 关闭进程池
    pool.close()
    pool.join()
    end = time.time()
    run = end - start
    print(f"===全部完成,用时{run}s===")
​
爬取结果

以下是MySQL的数据库结果查询:

性能分析

使用多进程,multiprocessing库,每一部电影单独一个driver窗口。

但是在爬取过程中,为了应对反爬机制,每页影评会强制time.sleep(1)停止一秒,因为被豆瓣封了几次ip,舍弃一部分速度换取程序的稳定性也是很必要的。

7.3 串联爬取结果与性能分析(短影评部分)

任务要求

取2022年度电影的最高评分韩国电影,但因为这一类榜单中只有5部,又加入了2022最高评分日本电影,总计10部电影,每部电影爬取10页影评,存入csv文件。

程序代码
 
from selenium import webdriver
from selenium.webdriver.common.by import By
from bs4 import BeautifulSoup
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import csv
import time
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.keys import Keys
​
def login_with_cookies(driver):
​
    # 访问网站
    driver.get("https://movie.douban.com/")
​
    # 添加新的Cookie
    cookies = {
        '__utma': '223695111.275596860.1695101450.1695101450.1695265771.2',
        # cookies打码一下
        # ........        
        'll': '"118282"'
    }
​
    for key, value in cookies.items():
        driver.add_cookie({'name': key, 'value': value})
​
    # 可以看到已登录
    driver.refresh()
​
#计时开始
start = time.time()
# 创建一个浏览器实例
driver = webdriver.Firefox()
login_with_cookies(driver)
​
# 2022年度榜单
wait = WebDriverWait(driver, 30)
page_2022_link = wait.until(
    EC.element_to_be_clickable((By.PARTIAL_LINK_TEXT, "2022年度榜单"))
)
page_2022_link.click()
​
movie_links = [
    {'url': 'https://movie.douban.com/subject/35073886/?source=2022_annual_movie', 'name': '分手的决心 헤어질 결심'},
    {'url': 'https://movie.douban.com/subject/35160926/?source=2022_annual_movie', 'name': '狩猎 헌트'},
    {'url': 'https://movie.douban.com/subject/35441582/?source=2022_annual_movie', 'name': '6/45 육사오'},
    {'url': 'https://movie.douban.com/subject/30267287/?source=2022_annual_movie', 'name': '犯罪都市2 범죄도시2'},
    {'url': 'https://movie.douban.com/subject/35743103/?source=2022_annual_movie', 'name': '小说家的电影 소설가의 영화'},
    {'url': 'https://movie.douban.com/subject/35015968/?source=2022_annual_movie', 'name': '昨日的美食 电影版'},
    {'url': 'https://movie.douban.com/subject/35597426/?source=2022_annual_movie', 'name': '稍微想起一些'},
    {'url': 'https://movie.douban.com/subject/34809360/?source=2022_annual_movie', 'name': '在街上'},
    {'url': 'https://movie.douban.com/subject/34905647/?source=2022_annual_movie', 'name': '由宇子的天平'},
    {'url': 'https://movie.douban.com/subject/35372792/?source=2022_annual_movie', 'name': '老师,您能坐在我旁边吗?'}
​
]
​
with open('Korea_movies_2022_comments.csv', 'w', newline='', encoding='utf-8') as csv_file:
    fieldnames = ['电影名称','用户名', '评论时间', '评论内容', '评分等级', '点赞数']
    writer = csv.DictWriter(csv_file, fieldnames=fieldnames)
    writer.writeheader()
    for movie_info in movie_links:
        print("===",movie_info['name'],"===")
        # 访问电影的评论页面
        driver.get(movie_info['url'])
        # 使用相对XPath来查找"更多短评"链接
        more_comments_link = driver.find_element(By.XPATH, "//a[contains(text(), '更多短评')]")
        more_comments_link.click()
        print("从第一页开始")
​
​
        for i in range(1, 11):
            # 使用WebDriverWait等待评论块元素加载
            comment_blocks = WebDriverWait(driver, 30).until(
                EC.presence_of_all_elements_located((By.CSS_SELECTOR, 'div.comment-item'))
            )
            print("本页评论加载完成")
            # 获取短评页面的HTML源代码
            short_comment_page_source = driver.page_source
​
            # 使用BeautifulSoup解析HTML源代码
            soup = BeautifulSoup(short_comment_page_source, 'html.parser')
​
            # 找到所有的短评块
            comment_blocks = soup.find_all('div', class_='comment-item')
​
            # 遍历每个短评块并提取信息
            for comment_block in comment_blocks:
                # 用户名
                username = comment_block.find('span', class_='comment-info').find('a').text.strip()
​
                # 评论时间
                comment_time = comment_block.find('span', class_='comment-time').text.strip()
​
                # 评论内容
                comment_content = comment_block.find('span', class_='short').text.strip()
                # 将换行符替换为空格
                comment_content = comment_content.replace('\n', ' ')
​
​
                # 找到包含评分星级的<span>标签
                rating_span = comment_block.find('span', class_=lambda x: x and 'allstar' in x)
                if rating_span:
                    # 获取<span>标签的class属性值
                    class_value = rating_span['class']
​
                    # 从class属性值中提取评分等级
                    rating = class_value[0].replace('allstar', '')
                else:
                    rating = "未评分"
​
                # 点赞数
                vote = comment_block.find('span', class_='votes').text.strip()
​
                # 将信息写入CSV文件
                writer.writerow({'电影名称': movie_info['name'],'用户名': username, '评论时间': comment_time, '评论内容': comment_content, '评分等级': rating, '点赞数': vote})
​
            print(f"===第{i}页已经爬取完毕===")
​
            # 寻找下一页短评的元素,并等待它可点击
            next_page = WebDriverWait(driver, 30).until(
                EC.element_to_be_clickable((By.CLASS_NAME, "next"))
            )
            # 滚动到下一页元素位置
            driver.execute_script("arguments[0].scrollIntoView();", next_page)
            # 然后再点击
            next_page.click()
end = time.time()
run = end - start
print(f"===全部完成,用时{run}s===")

性能分析

用时是120.92306709289551秒(仅供参考,

可能会多测试几次运行时间:

7.4 高阶爬虫结果与性能分析(短影评部分)

任务要求

取2022年度电影的最高评分韩国电影,但因为这一类榜单中只有5部,又加入了2022最高评分日本电影,总计10部电影,每部电影爬取10页影评,存入csv文件。并加入以下技术:

(1)多进程多线程技术

(2)数据库交互( redis/mongodb/mysql )

(3)虚拟地址

可能遇到困难:反爬虫机制

程序代码
 
create database Douban character set utf8;
use douban;
select * from comments;
truncate comments; // 用于测试之前清楚表内数据
from selenium import webdriver
from selenium.webdriver.common.by import By
from bs4 import BeautifulSoup
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException
import random
import time
import pymysql
import multiprocessing
​
​
def ip_random():
    #  developer tools -> Network -> Header -> Response header [raw]
    # 修改为自己浏览器的headers
    # Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/117.0
    headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/117.0'}
    '''
    前版本代码
    API_link = 'https://api.hailiangip.com:8522/api/getIpEncrypt?dataType=1&encryptParam=1Pofnwjjlmawt1HAVLj6tTxUJo8Ctj43ciCg9jb8LL9ZnRSnD61wqH3N2dPDshZ9%2B3kkTAQZ2MD%2FIbJREwKEhHndyjBrABrC6w9Kgqc82yZyTTUse5IUp%2B%2Bl1F%2FVhbLSlhmBtWyY09Uxpbc64mJ2a3wBfYbfJtr8aD2UGympj%2FHBd1HPaJFym4rBQnOJ7ZU9arf5KKS7AFD8J5Z1OJJ%2FWbhiKf6bPqRShUoN9E1%2FgqE%3D'
    response = requests.get(url=API_link, headers=headers)
    # IP地址拆分
    IP_list = response.text.split("\n")
    proxy_list = []
    '''
    IP_list = [
        "114.232.109.213",
        "113.124.216.126",
        "60.170.204.30",
        "113.121.21.191",
        "61.216.185.88",
        "182.34.103.132",
        "47.97.191.179  ",
        "111.3.102.207",
        "114.231.41.224",
        "27.214.51.122",
        "39.99.54.91",
        "60.170.204.30",
        "60.205.132.71",
        "218.75.102.198",
        "113.121.47.118",
        "113.121.42.177",
        "60.170.204.30",
        "183.236.232.160",
        "182.34.101.96"
    ]
    proxy_list = []
    # prefix = 'https:' 前版本代码
    prefix = 'http:'
    for ip in IP_list:
        if ip != '':
            # 添加网页前缀
            proxy_list.append(prefix + ip)
    proxy = random.choice(proxy_list)
    return proxy
​
def login_with_cookies(driver):
    # 获取代理IP
    proxy = ip_random()
    print("使用代理IP:", proxy)
    # 设置代理
    webdriver.DesiredCapabilities.FIREFOX['proxy'] = {
        "httpProxy": proxy,
        "ftpProxy": proxy,
        "sslProxy": proxy,
        "proxyType": "MANUAL"
    }
​
    # 访问网站
    driver.get("https://movie.douban.com/")
​
    # 添加新的Cookie
    cookies = {

​
    }
​
    for key, value in cookies.items():
        driver.add_cookie({'name': key, 'value': value})
​
    # 可以看到已登录
    driver.refresh()
​
# 链接 MySQL
conn = pymysql.connect(
    host='localhost',
    user='root',
    password='20031008',
    database='Douban',
    charset='utf8mb4'
)
# 创建游标
cursor = conn.cursor()
​
​
# 将数据插入数据库
def insert_data_to_mysql(movie_info, username, comment_time, comment_content, rating, vote):
    try:
        # SQL插入语句
        sql = "INSERT INTO comments (movie_name, username, comment_time, comment_content, rating, vote) VALUES (%s, %s, %s, %s, %s, %s)"
        # 执行SQL插入操作
        cursor.execute(sql, (movie_info, username, comment_time, comment_content, rating, vote))
        # 提交更改
        conn.commit()
    except Exception as e:
        # 如果出现错误,回滚更改
        print(f"插入数据时出错: {str(e)}")
        conn.rollback()
​
# 创建代理IP池
proxy_pool = []
​
def init_proxy_pool():
    for _ in range(10):  # 创建10个代理IP
        proxy = ip_random()
        proxy_pool.append(proxy)
​
def get_next_proxy():
    if len(proxy_pool) == 0:
        init_proxy_pool()  # 如果代理池为空,重新初始化
    return proxy_pool.pop()  # 获取下一个代理IP,并从池中移除
​
def scrape_and_store(movie_info):
    # 创建一个浏览器实例
    driver = webdriver.Firefox()
    login_with_cookies(driver)
    try:
        # 获取代理IP
        proxy = get_next_proxy()
        print("使用代理IP:", proxy)
​
        # 设置代理
        webdriver.DesiredCapabilities.FIREFOX['proxy'] = {
            "httpProxy": proxy,
            "ftpProxy": proxy,
            "sslProxy": proxy,
            "proxyType": "MANUAL"
        }
​
        # 获取电影评论页面的URL
        movie_url = movie_info['url']
        movie_name = movie_info['name']
        print("===", movie_name, "===")
        
        # 访问电影的评论页面
        driver.get(movie_url)
        # 使用相对XPath来查找"更多短评"链接
        more_comments_link = WebDriverWait(driver, 30).until(
        EC.presence_of_element_located((By.XPATH, "//a[contains(text(), '更多短评')]"))
        )
        more_comments_link.click()
        print("从第一页开始")
​
        for i in range(1, 11):
            # 使用WebDriverWait等待评论块元素加载
            comment_blocks = WebDriverWait(driver, 30).until(
                EC.presence_of_all_elements_located((By.CSS_SELECTOR, 'div.comment-item'))
            )
            print("本页评论加载完成")
            # 获取短评页面的HTML源代码
            short_comment_page_source = driver.page_source
​
            # 使用BeautifulSoup解析HTML源代码
            soup = BeautifulSoup(short_comment_page_source, 'html.parser')
​
            # 找到所有的短评块
            comment_blocks = soup.find_all('div', class_='comment-item')
​
            time.sleep(0.5)
            # 遍历每个短评块并提取信息
            for comment_block in comment_blocks:
                # 用户名
                username = comment_block.find('span', class_='comment-info').find('a').text.strip()
​
                # 评论时间
                comment_time = comment_block.find('span', class_='comment-time').text.strip()
​
                # 评论内容
                comment_content = comment_block.find('span', class_='short').text.strip()
                # 将换行符替换为空格
                comment_content = comment_content.replace('\n', ' ')
​
                # 找到包含评分星级的<span>标签
                rating_span = comment_block.find('span', class_=lambda x: x and 'allstar' in x)
                if rating_span:
                    # 获取<span>标签的class属性值
                    class_value = rating_span['class']
​
                    # 从class属性值中提取评分等级
                    rating = class_value[0].replace('allstar', '')
                else:
                    rating = "未评分"
​
                # 点赞数
                vote = comment_block.find('span', class_='votes').text.strip()
​
                # 处理包含特殊字符的字符串
                username = username.encode('utf-8', 'ignore').decode('utf-8')
                comment_content = comment_content.encode('utf-8', 'ignore').decode('utf-8')
​
                # 写入MySQL
                insert_data_to_mysql(movie_name, username, comment_time, comment_content, rating, vote)
​
            print(f"===第{i}页已经爬取完毕")
​
            # 寻找下一页短评的元素,并等待它可点击
            next_page = WebDriverWait(driver, 30).until(
                EC.element_to_be_clickable((By.CLASS_NAME, "next"))
            )
            # 滚动到下一页元素位置
            driver.execute_script("arguments[0].scrollIntoView();", next_page)
            # 然后再点击
            time.sleep(0.5)
            next_page.click()
​
    except Exception as e:
        print(f"爬取电影评论时出错: {str(e)}")
    finally:
        # 关闭浏览器
        driver.quit()
        # 将代理IP放回池中,以便下次使用
        proxy_pool.append(proxy)
​
​
if __name__ == "__main__":
    
    start = time.time()
    # 创建多进程池
    pool = multiprocessing.Pool(processes=11)
​
​
    # 2022年度榜单
    movie_links = [
        {'url': 'https://movie.douban.com/subject/35073886/?source=2022_annual_movie', 'name': '分手的决心'},
        {'url': 'https://movie.douban.com/subject/35160926/?source=2022_annual_movie', 'name': '狩猎'},
        {'url': 'https://movie.douban.com/subject/35441582/?source=2022_annual_movie', 'name': '6/45'},
        {'url': 'https://movie.douban.com/subject/30267287/?source=2022_annual_movie', 'name': '犯罪都市2'},
        {'url': 'https://movie.douban.com/subject/35743103/?source=2022_annual_movie', 'name': '小说家的电影'},
​
        {'url': 'https://movie.douban.com/subject/35015968/?source=2022_annual_movie', 'name': '昨日的美食 电影版'},
        {'url': 'https://movie.douban.com/subject/35597426/?source=2022_annual_movie', 'name': '稍微想起一些'},
        {'url': 'https://movie.douban.com/subject/34809360/?source=2022_annual_movie', 'name': '在街上'},
        {'url': 'https://movie.douban.com/subject/34905647/?source=2022_annual_movie', 'name': '由宇子的天平'},
        {'url': 'https://movie.douban.com/subject/35372792/?source=2022_annual_movie', 'name': '老师,您能坐在我旁边吗?'}
    ]
​
​
    # 将每个电影链接分配给不同的进程
    pool.map(scrape_and_store, movie_links)
​
​
    # 关闭游标和数据库连接
    cursor.close()
    conn.close()
    # 关闭进程池
    pool.close()
    pool.join()
    end = time.time()
    run = end - start
    print(f"===全部完成,用时{run}s===")

9、参考文献

  • 爬虫进阶:反反爬虫技术--5 网页隐藏信息

爬虫进阶:反反爬虫技术--5 网页隐藏信息_爬虫 display: none-CSDN博客

  • python网络爬虫系列(三)——cookie的原理、保存与加载

python网络爬虫系列(三)——cookie的原理、保存与加载_将获取到的数据存到cookies,每次刷新页面之后将cookies加载到某个div-CSDN博客

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值