20241319 2024-2025-2 《Python程序设计》实验四报告
20241319 2024-2025-2 《Python程序设计》实验四报告
课程:《Python程序设计》
班级: 2413
姓名: 吴辰曦
学号:20241319
实验教师:王志强
实验日期:2025年3月26日
必修/选修: 公选课
1.实验内容
Python综合应用:爬虫、数据处理、可视化、机器学习、神经网络、游戏、网络安全等。
课代表和各小组负责人收集作业(源代码、视频、综合实践报告)
Python综合应用:爬虫、数据处理、可视化、机器学习、神经网络、游戏、网络安全等。
例如:编写从社交网络爬取数据,实现可视化舆情监控或者情感分析。
例如:利用公开数据集,开展图像分类、恶意软件检测等
例如:利用Python库,基于OCR技术实现自动化提取图片中数据,并填入excel中。
例如:爬取天气数据,实现自动化微信提醒
例如:利用爬虫,实现自动化下载网站视频、文件等。
例如:编写小游戏:坦克大战、贪吃蛇、扫雷等等
2.实验过程及结果
(1)选题:由于我喜欢看电影解说,所以我想的是制作一个爬取豆瓣top 的爬虫
(3)过程:原本只是想做一个由requests库和Beautifulsoup制作的简单爬虫,但似乎豆瓣的反爬机制限制了我的访问,于是我对我的代码进行了第一次升级——selenium库,而经过一系列调试后,可以爬取一些内容,但仅限于电影名称、上映时间和评分,于是代码又经过了一次升级——Scrapy库,终于,现在能完成大部分我所想要的内容了,但仍有些小问题存在
最终代码
点击查看代码
# -*- coding: utf-8 -*-
import scrapy
import re
import time
import random
import pandas as pd
import json
import logging
from scrapy.crawler import CrawlerProcess
class DoubanMovieItem(scrapy.Item):
# 基础信息
rank = scrapy.Field() # 排名
title = scrapy.Field() # 电影名称
rating = scrapy.Field() # 评分
year = scrapy.Field() # 上映年份
director = scrapy.Field() # 导演
actors = scrapy.Field() # 主演
quote = scrapy.Field() # 经典台词
# 扩展信息
genres = scrapy.Field() # 类型
duration = scrapy.Field() # 片长
region = scrapy.Field() # 制片国家/地区
language = scrapy.Field() # 语言
imdb_link = scrapy.Field() # IMDb链接
synopsis = scrapy.Field() # 剧情简介
rating_count = scrapy.Field() # 评价人数
crawl_time = scrapy.Field() # 爬取时间
class DoubanTop250Spider(scrapy.Spider):
name = 'douban_top250'
allowed_domains = ['movie.douban.com']
start_urls = ['https://movie.douban.com/top250']
# 自定义设置(反爬优化)
custom_settings = {
'DOWNLOAD_DELAY': 2.5, # 请求延迟(秒)
'CONCURRENT_REQUESTS': 3, # 并发请求数
'RETRY_TIMES': 3, # 失败重试次数
'COOKIES_ENABLED': False, # 禁用Cookies
'USER_AGENT': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36',
'FEED_EXPORT_ENCODING': 'utf-8',
'LOG_LEVEL': 'INFO',
'DEFAULT_REQUEST_HEADERS': { # 完整请求头
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'zh-CN,zh;q=0.9',
'Referer': 'https://movie.douban.com/top250',
'Connection': 'keep-alive'
}
}
def start_requests(self):
"""生成分页请求"""
for page in range(0, 250, 25):
url = f'https://movie.douban.com/top250?start={page}'
yield scrapy.Request(
url=url,
callback=self.parse,
meta={'delay': 1.5 + 2 * random.random()} # 随机延迟(1.5-3.5秒)
)
def parse(self, response):
"""解析列表页"""
for movie in response.css('.item'):
try:
item = DoubanMovieItem()
# 基础字段提取
item['rank'] = movie.css('.pic em::text').get()
item['title'] = movie.css('.title::text').get()
item['rating'] = movie.css('.rating_num::text').get()
item['rating_count'] = movie.css('.star span:last-child::text').re_first(r'(\d+)人评价')
# 增强版导演/演员解析
info_text = ''.join(movie.css('.bd p::text').getall())
# 导演信息(带安全检测)
if '导演:' in info_text:
director_match = re.search(r'导演:\s*(.*?)(?:\s*主演:|$)', info_text)
item['director'] = director_match.group(1).strip() if director_match else ""
else:
item['director'] = ""
# 演员信息(带安全检测)
if '主演:' in info_text:
actors_match = re.search(r'主演:\s*(.*?)(?:\s*类型:|$)', info_text)
item['actors'] = actors_match.group(1).strip() if actors_match else ""
else:
item['actors'] = ""
# 年份信息
year_match = re.search(r'(\d{4})', info_text)
item['year'] = year_match.group(1) if year_match else ""
# 经典台词
item['quote'] = movie.css('.inq::text').get() or ""
# 详情页链接
detail_url = movie.css('.hd a::attr(href)').get()
# 发起详情页请求
yield response.follow(
url=detail_url,
callback=self.parse_detail,
meta={'item': item},
priority=10
)
except Exception as e:
self.logger.error(f"列表页解析异常: {str(e)}")
def parse_detail(self, response):
"""解析详情页"""
item = response.meta['item']
try:
# 类型和时长
item['genres'] = '/'.join(response.css('span[property="v:genre"]::text').getall())
item['duration'] = response.css('span[property="v:runtime"]::attr(content)').get()
# 从info区块提取多语言字段
info_html = response.css('#info').get() or ""
# 国家/地区(三重匹配方案)
region_match = re.search(r'制片国家/地区:</span>(.*?)<br', info_html) or \
re.search(r'地区:</span>(.*?)<br', info_html) or \
re.search(r'国家:</span>(.*?)<br', info_html)
item['region'] = region_match.group(1).strip() if region_match else ""
# 语言(双重匹配方案)
lang_match = re.search(r'语言:</span>(.*?)<br', info_html) or \
re.search(r'语言:</span>(.*?)$', info_html)
item['language'] = lang_match.group(1).strip() if lang_match else ""
# IMDb链接
item['imdb_link'] = response.xpath('//a[contains(@href, "imdb.com")]/@href').get()
# 剧情简介
synopsis = response.css('span[property="v:summary"]::text').getall()
item['synopsis'] = ''.join(synopsis).strip() if synopsis else ""
# 爬取时间戳
item['crawl_time'] = time.strftime("%Y-%m-%d %H:%M:%S")
yield item
except Exception as e:
self.logger.error(f"详情页解析异常: {str(e)}")
# 返回已解析的基础信息
item['genres'] = ""
item['duration'] = ""
item['region'] = ""
item['language'] = ""
item['imdb_link'] = ""
item['synopsis'] = ""
yield item
class DataPipeline:
"""数据存储管道"""
def __init__(self):
self.items = []
def process_item(self, item, spider):
spider.logger.info(f"已收集: {item.get('title', '未知电影')}")
self.items.append(dict(item))
return item
def close_spider(self, spider):
"""爬虫结束时保存数据"""
import os
from datetime import datetime
if not self.items:
spider.logger.error("未收集到任何数据,跳过文件生成")
return
try:
# 创建专用输出目录
output_dir = "douban_top250_data"
os.makedirs(output_dir, exist_ok=True)
# 验证目录写入权限
test_file = os.path.join(output_dir, "write_test.txt")
with open(test_file, "w", encoding="utf-8") as f:
f.write("权限测试 - 成功")
os.remove(test_file)
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
spider.logger.info(f"准备保存{len(self.items)}条数据")
# 保存为Excel
excel_file = os.path.join(output_dir, f"豆瓣TOP250_{timestamp}.xlsx")
df = pd.DataFrame(self.items)
df.to_excel(excel_file, index=False, encoding='utf-8-sig')
spider.logger.info(f"Excel文件已生成: {excel_file}")
# 保存为JSON
json_file = os.path.join(output_dir, f"豆瓣TOP250_{timestamp}.json")
with open(json_file, 'w', encoding='utf-8') as f:
json.dump(self.items, f, ensure_ascii=False, indent=2)
spider.logger.info(f"JSON文件已生成: {json_file}")
# 保存为TXT
txt_file = os.path.join(output_dir, f"豆瓣TOP250_{timestamp}.txt")
with open(txt_file, 'w', encoding='utf-8') as f:
for idx, item in enumerate(self.items):
f.write(f"=== 电影#{idx + 1} ===\n")
f.write(f"排名: {item.get('rank', '')}\n")
f.write(f"标题: {item.get('title', '')}\n")
f.write(f"评分: {item.get('rating', '')} ({item.get('rating_count', '0')}人评价)\n")
f.write(f"年份: {item.get('year', '')}\n")
f.write(f"导演: {item.get('director', '')}\n")
f.write(f"主演: {item.get('actors', '')}\n")
f.write(f"类型: {item.get('genres', '')}\n")
f.write(f"地区: {item.get('region', '')}\n")
f.write(f"IMDb: {item.get('imdb_link', '')}\n")
f.write(f"名言: {item.get('quote', '')}\n")
f.write(f"简介: {item.get('synopsis', '')[:100]}...\n")
f.write("-" * 60 + "\n\n")
spider.logger.info(f"TXT文件已生成: {txt_file}")
spider.logger.info(f"数据保存完成!共保存{len(self.items)}条记录")
except Exception as e:
spider.logger.error(f"‼文件保存失败: {str(e)}")
# 记录详细错误
error_log = os.path.join(output_dir, f"error_{timestamp}.log")
with open(error_log, "w", encoding="utf-8") as err_f:
err_f.write(f"错误时间: {datetime.now()}\n")
err_f.write(f"错误信息: {str(e)}\n")
err_f.write("收集到的数据:\n")
for item in self.items:
err_f.write(f"{item}\n")
# 独立运行脚本
if __name__ == "__main__":
print("=" * 60)
print("豆瓣TOP250专业爬虫 v8.0 (终极修复版)")
print("=" * 60)
# 配置Scrapy
process = CrawlerProcess(settings={
'DOWNLOAD_DELAY': 2.5,
'CONCURRENT_REQUESTS': 3,
'RETRY_TIMES': 3,
'USER_AGENT': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36',
'ITEM_PIPELINES': {'__main__.DataPipeline': 300},
'TELNETCONSOLE_ENABLED': True # 启用Telnet调试
})
process.crawl(DoubanTop250Spider)
process.start()

爬取的信息:
3.实验过程中遇到的的问题
(1)在1.0requests和Beautifulsoup版本里,代码爬取不到任何信息
(2)在2.0selenium版里爬取的内容极其有限
(3)3.0Scrapy版本里仍有一些内容爬取失败
解决方案:使用了强大的deepseek祝我一臂之力
课程总结与感悟
最后的实验,让我知道了我在python上的学习了解还很浅薄,在有AI的帮助下,仍耗费了大量的时间才堪堪完成了一个简单的爬虫程序。
最后,也感谢王老师在这学期python公选课带领我入门python这门有趣的语言。
纸上得来终觉浅,绝知此事要躬行!

浙公网安备 33010602011771号