20232223 实验四《Python程序设计》实验报告
20232223 2025-2026-2 《Python程序设计》实验四报告
课程:《Python程序设计》
班级: 2322
姓名: 夏韵诗
学号:20232223
实验教师:王志强
实验日期:2026年5月25日
必修/选修: 公选课
1.实验内容
Python综合实践
使用爬虫爬取每日资讯,并实现自动化微信多联系人群发资讯,定时自动任务并将咨询去重CSV存档。
2. 实验过程及结果
2.1实验分析
此次实验主要完成以下功能:
- 运用Scrapy爬虫框架抓取央视官网资讯,通过CSV文件存储历史新闻,自动过滤重复资讯;
- 基于 pywinauto 控件操控电脑微信,读取配置文件批量向多名联系人/群组推送资讯;
- 搭建定时自动化任务,可自定义周期自动执行爬虫抓取与微信群发全套流程。
因此本实验涉及到网络爬虫的使用和Python与微信界面的交互问题,需要依赖Python强大的库作为支撑,更好地实现上述功能
2.2实验设计
为了便于获取需要的资讯信息,过滤掉其他无关信息,本实验选取了Scrapy框架,通过框架的整体结构和内嵌的XPath筛查定位功能来对网页信息进行定位获取。

为了实现Python与微信界面的交互,摒弃了依赖固定屏幕坐标的pyautogui鼠标模拟方式(该方式受屏幕分辨率、窗口大小影响,通用性差),使用pywinauto库基于窗口句柄与界面控件操控电脑版微信,不受屏幕尺寸、窗口位置限制,稳定性更强,同时也能有效降低被微信判定为机器人封号的风险。
程序结合微信原生快捷键完成搜索、打开聊天窗口、输入消息等操作,并搭配pyperclip库解决中文输入难题:将中文内容写入系统剪贴板,再模拟粘贴操作,实现正常中文发送。

为解决爬虫模块与微信发送模块的信息传递问题,本实验将两个功能拆分实现:爬虫脚本先将爬取的资讯列表转为标准字符串,以utf-8编码写入本地文本文件;微信发送脚本读取该文件内容并解码,再将内容送入剪贴板完成发送。

为解决原执行逻辑中Scrapy阻塞主线程、跨脚本调用失效的问题,使用subprocess子进程方式调用爬虫命令,增加文件校验与延时等待,保证爬虫执行完毕、文件写入完成后,再自动执行微信发送逻辑,实现一键全流程自动化运行

考虑到推送咨询时可能需要批量群发多联系人,拓展后采用配置文件解耦联系人信息,新建contacts.txt文本存储所有需要推送的好友、群聊名称,程序读取文件生成联系人列表,循环遍历逐个执行微信消息发送,每发送一人增加延时,避免操作过快触发微信风控限制,实现一次爬取、批量全员推送。

原本的程序每次爬取直接覆盖资讯文件,会重复推送旧新闻且无历史记录,而我们每次只希望看到新的咨询,有时也需要查询历史咨询,因此新增CSV持久化存档机制,文件news_history.csv记录每条新闻标题与爬取时间;爬虫执行前读取全部历史新闻存入集合快速比对,仅从未出现过的全新资讯写入推送文本,同时追加存入CSV永久保存,过滤重复内容,防止重复消息骚扰联系人。

为了实现定时推送自动化,而非每次都要点击运行按键,我新增了定时总控脚本timer_run.py,采用无限循环+延时休眠实现无人值守自动执行,可自定义任务间隔(测试阶段设40秒快速调试,正式使用可根据需求设置);每次循环自动完成爬虫抓取、新闻去重、批量群发全流程,配套日志文件run_log.txt记录每轮运行时间与执行状态,方便排查运行异常。

2.3实现过程
在本实验进行之前应当首先初步学习Scrapy框架。
Scrapy是用Python实现的一个为了爬取网站数据、提取结构性数据而编写的应用框架。
Scrapy常应用在包括数据挖掘,信息处理或存储历史数据等一系列的程序中。
Scrapy的基本工作原理如下图所示

- Scrapy Engine(引擎): 负责Spider、ItemPipeline、Downloader、Scheduler中间的通讯,信号、数据传递等。
- Scheduler(调度器): 负责接受引擎发送过来的Request请求,并按照一定的方式进行整理排列,入队,当引擎需要时,交还给引擎。
- Downloader(下载器):负责下载Scrapy Engine(引擎)发送的所有Requests请求,并将其获取到的Responses交还给Scrapy Engine(引擎),由引擎交给Spider来处理,
- Spider(爬虫):负责处理所有Responses,从中分析提取数据,获取Item字段需要的数据,并将需要跟进的URL提交给引擎,再次进入Scheduler(调度器).
- Item Pipeline(管道):负责处理Spider中获取到的Item,并进行进行后期处理(详细分析、过滤、存储等)的地方。
- Downloader Middlewares(下载中间件):可以当作是一个可以自定义扩展下载功能的组件。
- Spider Middlewares(Spider中间件):可以理解为是一个可以自定扩展和操作引擎和Spider中间通信的功能组件(比如进入Spider的Responses;和从Spider出去的Requests)
如果要使用Scrapy需要进行安装、项目建立、爬虫生成:
- 安装:在命令行模式下输入以下代码:
pip install scrapy
- 项目建立:在命令行模式下输入以下代码:
scrapy startproject mySpider
- 爬虫生成:在命令行模式下输入以下代码:
scrapy genspider cctv "cctv.cn"
完成上述步骤之后可以出现前面图片中出现的目录
然后我们再对一些文件进行设置
setting.py:
# Scrapy settings for DailyNews project
#
# For simplicity, this file contains only settings considered important or
# commonly used. You can find more settings consulting the documentation:
#
# https://docs.scrapy.org/en/latest/topics/settings.html
# https://docs.scrapy.org/en/latest/topics/downloader-middleware.html
# https://docs.scrapy.org/en/latest/topics/spider-middleware.html
BOT_NAME = "DailyNews"
SPIDER_MODULES = ["DailyNews.spiders"]
NEWSPIDER_MODULE = "DailyNews.spiders"
# Crawl responsibly by identifying yourself (and your website) on the user-agent
USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537"
# Obey robots.txt rules
ROBOTSTXT_OBEY = False
# Configure maximum concurrent requests performed by Scrapy (default: 16)
#CONCURRENT_REQUESTS = 32
# Configure a delay for requests for the same website (default: 0)
# See https://docs.scrapy.org/en/latest/topics/settings.html#download-delay
# See also autothrottle settings and docs
#DOWNLOAD_DELAY = 3
# The download delay setting will honor only one of:
#CONCURRENT_REQUESTS_PER_DOMAIN = 16
#CONCURRENT_REQUESTS_PER_IP = 16
# Disable cookies (enabled by default)
#COOKIES_ENABLED = False
# Disable Telnet Console (enabled by default)
#TELNETCONSOLE_ENABLED = False
# Override the default request headers:
DEFAULT_REQUEST_HEADERS = {
"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537",
'Accept': "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
'Accept-Language': "en",
}
# Enable or disable spider middlewares
# See https://docs.scrapy.org/en/latest/topics/spider-middleware.html
#SPIDER_MIDDLEWARES = {
# "DailyNews.middlewares.DailynewsSpiderMiddleware": 543,
#}
# Enable or disable downloader middlewares
# See https://docs.scrapy.org/en/latest/topics/downloader-middleware.html
#DOWNLOADER_MIDDLEWARES = {
# "DailyNews.middlewares.DailynewsDownloaderMiddleware": 543,
#}
# Enable or disable extensions
# See https://docs.scrapy.org/en/latest/topics/extensions.html
#EXTENSIONS = {
# "scrapy.extensions.telnet.TelnetConsole": None,
#}
# Configure item pipelines
# See https://docs.scrapy.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
"DailyNews.pipelines.DailynewsPipeline": 300,
}
# Enable and configure the AutoThrottle extension (disabled by default)
# See https://docs.scrapy.org/en/latest/topics/autothrottle.html
#AUTOTHROTTLE_ENABLED = True
# The initial download delay
#AUTOTHROTTLE_START_DELAY = 5
# The maximum download delay to be set in case of high latencies
#AUTOTHROTTLE_MAX_DELAY = 60
# The average number of requests Scrapy should be sending in parallel to
# each remote server
#AUTOTHROTTLE_TARGET_CONCURRENCY = 1.0
# Enable showing throttling stats for every response received:
#AUTOTHROTTLE_DEBUG = False
# Enable and configure HTTP caching (disabled by default)
# See https://docs.scrapy.org/en/latest/topics/downloader-middleware.html#httpcache-middleware-settings
#HTTPCACHE_ENABLED = True
#HTTPCACHE_EXPIRATION_SECS = 0
#HTTPCACHE_DIR = "httpcache"
#HTTPCACHE_IGNORE_HTTP_CODES = []
#HTTPCACHE_STORAGE = "scrapy.extensions.httpcache.FilesystemCacheStorage"
# Set settings whose default value is deprecated to a future-proof value
REQUEST_FINGERPRINTER_IMPLEMENTATION = "2.7"
TWISTED_REACTOR = "twisted.internet.asyncioreactor.AsyncioSelectorReactor"
FEED_EXPORT_ENCODING = "utf-8"
LOG_LEVEL = "WARNING"
通过以上设置,可以伪装访问请求头,解决网站拒绝访问的问题,还可以避免打印出日志信息
items.py:
# Define here the models for your scraped items
#
# See documentation in:
# https://docs.scrapy.org/en/latest/topics/items.html
import scrapy
class DailynewsItem(scrapy.Item):
# define the fields for your item here like:
content = scrapy.Field()
通过以上设置,可以创建出包含所需信息的Item对象
pipelines.py:
# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html
import csv
# useful for handling different item types with a single interface
from itemadapter import ItemAdapter
class DailynewsPipeline:
# def __init__(self):
# self.f=open('DailyNews.txt','w',encoding="utf-8",newline='')
# self.file_name=['content']
# self.writer=csv.DictWriter(self.f,fieldnames=self.file_name)
# self.writer.writeheader()
def process_item(self, item, spider):
# self.writer.writerow(dic(item))
print(item)
return item
# def close_spider(self,spider):
# self.f.close()
piplines中预编写了将信息写入.csv文件中的代码,读者可以自取
爬虫文件cctv.py:
import scrapy
import os
import csv
from datetime import datetime
from DailyNews.items import DailynewsItem
HISTORY_CSV = "./news_history.csv"
def get_history_news():
history_set = set()
if not os.path.exists(HISTORY_CSV):
with open(HISTORY_CSV, "w", encoding="utf-8", newline="") as f:
writer = csv.writer(f)
writer.writerow(["crawl_time", "news_title"])
return history_set
with open(HISTORY_CSV, "r", encoding="utf-8") as f:
reader = csv.reader(f)
next(reader)
for row in reader:
if len(row) >= 2 and row[1].strip():
history_set.add(row[1].strip())
return history_set
def save_news_to_history(new_list):
crawl_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
with open(HISTORY_CSV, "a", encoding="utf-8", newline="") as f:
writer = csv.writer(f)
for title in new_list:
writer.writerow([crawl_time, title])
class CctvSpider(scrapy.Spider):
name = "cctv"
allowed_domains = ["cctv.com"]
start_urls = ["https://www.cctv.com/"]
def parse(self, response):
filename = "DailyNews.txt"
num = 0
all_news = []
new_news = []
history = get_history_news()
for each in response.xpath("//div[@class='txt']"):
content = each.xpath("a/text()").extract_first()
if content is not None:
content = content.strip()
all_news.append(content)
if content not in history:
new_news.append(content)
num = num + 1
print("页面全部新闻:", all_news)
print("本次新增新闻:", new_news)
f = open('./DailyNews.txt', 'w', encoding='utf-8', newline="")
if len(new_news) == 0:
f.write("暂无今日新资讯\n")
f.close()
print("无新增新闻,跳过存档与推送")
return new_news
context = str(new_news).replace(", ","\n").replace("[","").replace("]","").replace("'","#")
f.writelines("每日资讯\n")
f.writelines(context)
f.close()
save_news_to_history(new_news)
print(f"已将{len(new_news)}条新新闻存入历史存档news_history.csv")
return new_news
cctv.py文件既实现了信息的爬取,也将列表转化为字符串,去掉了无用信息,添加了标题,完成了分行,以utf-8编码的形式写入了文件。同时实现新闻去重逻辑与CSV历史存档功能。程序每次爬取前读取历史新闻记录,仅保留从未出现的全新资讯用于推送,并将所有新闻附带爬取时间永久存入本地存档文件。
微信发送文件send.py:
import pyperclip
import time
from pywinauto import Application
from pywinauto.findwindows import ElementNotFoundError
try:
with open("./DailyNews.txt", "r", encoding="utf-8") as f:
content = f.read().strip()
if not content:
print("资讯文件为空,无需发送")
exit()
except FileNotFoundError:
print("错误:未找到DailyNews.txt,请先运行爬虫!")
exit()
def get_contact_list():
contact_list = []
try:
with open("./contacts.txt", "r", encoding="utf-8") as f:
for line in f.readlines():
name = line.strip()
if name:
contact_list.append(name)
return contact_list
except FileNotFoundError:
print("未找到contacts.txt联系人配置文件!")
return []
def send_wechat_msg(contact_name: str, msg: str):
try:
app = Application(backend="uia").connect(title_re=".*微信.*")
wechat_win = app.window(title_re=".*微信.*")
wechat_win.set_focus()
time.sleep(0.8)
wechat_win.type_keys("^f")
time.sleep(0.5)
pyperclip.copy(contact_name)
wechat_win.type_keys("^v")
time.sleep(1)
wechat_win.type_keys("{ENTER}")
time.sleep(1.2)
pyperclip.copy(msg)
wechat_win.type_keys("^v")
time.sleep(0.5)
wechat_win.type_keys("{ENTER}")
print(f"✅ 成功发送给【{contact_name}】")
time.sleep(5)
except ElementNotFoundError:
print("❌ 未检测到电脑版微信,请手动打开并登录后重试!")
except Exception as e:
print(f"❌ 发送【{contact_name}】失败:{str(e)}")
def batch_send_all():
contact_arr = get_contact_list()
if len(contact_arr) == 0:
print("联系人列表为空,终止发送")
return
print(f"共读取到 {len(contact_arr)} 个联系人,开始批量发送...")
for name in contact_arr:
send_wechat_msg(name, content)
if __name__ == "__main__":
batch_send_all()
运行run_all.py,完成爬虫和微信发送:
import time
import os
import subprocess
print("===== 开始爬取资讯 =====")
ret = subprocess.run(["scrapy", "crawl", "cctv"], encoding="utf-8")
time.sleep(3)
print("===== 爬取完成,准备发送微信 =====")
os.system("python send.py")
定时运行脚本timer_run.py
import time
import subprocess
import os
from datetime import datetime
# 为测试暂定为40s 可根据实际需要调整
RUN_INTERVAL = 40
NEWS_FILE = "./DailyNews.txt"
LOG_FILE = "./run_log.txt"
def write_log(text):
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
log_str = f"[{now}] {text}\n"
with open(LOG_FILE, "a", encoding="utf-8") as f:
f.write(log_str)
print(log_str.strip())
def single_task():
write_log("===== 启动一轮定时任务 =====")
write_log("开始爬取新闻...")
subprocess.run(["scrapy", "crawl", "cctv"])
time.sleep(2)
if not os.path.exists(NEWS_FILE):
write_log("爬虫未生成新闻文件,跳过发送")
return
with open(NEWS_FILE, "r", encoding="utf-8") as f:
news_text = f.read().strip()
if not news_text:
write_log("本次无新资讯,跳过发送")
return
write_log("开始批量微信推送...")
os.system("python send.py")
write_log("本轮任务全部完成\n")
def timer_loop():
write_log("定时推送程序已启动,每6小时自动执行一次任务")
while True:
single_task()
write_log(f"等待{RUN_INTERVAL/3600}小时后执行下一轮...\n")
time.sleep(RUN_INTERVAL)
if __name__ == "__main__":
timer_loop()
注意事项
- 单次手动一键运行:执行python run_all.py,自动完成爬虫抓取、批量群发一次;
- 全自动定时循环运行:执行python timer_run.py,程序常驻后台,按设定周期自动执行整套流程;测试阶段间隔为40秒,正式使用可修改代码内RUN_INTERVAL为66060(6小时);
- 运行前必须提前手动打开并登录电脑版微信,无需调整窗口大小、分辨率;
- 修改推送对象:直接编辑根目录contacts.txt,增删好友/群聊名称即可;
- 程序会自动生成news_history.csv新闻历史存档、run_log.txt运行日志,无需手动创建;
- 设备卡顿可适当增大代码中time.sleep()延时,保证页面、文件加载完成。
2.4运行结果:




运行过程可观看录制视频。
实验过程中遇到的问题和解决过程
- 问题1:网站拒绝访问问题
- 问题1解决方案:在框架的设置文件之中伪造访问请求头
- 问题2:中文编码问题
- 问题2解决方案:中文直接写入文件后难以被读取出来,因此使用
utf-8编码的方式进行读写操作 - 问题3:中文输入问题
- 问题3解决方案:使用pyperclip包,直接将中文放入剪切板中,通过键盘操控复制粘贴,绕开了输入难题。
- 问题4:Scrapy运行阻塞主线程问题
- 问题4解决方案:使用subprocess子进程调用爬虫命令,解除主线程阻塞;同时增加文件存在性循环校验,确保资讯文件完全写入后,再执行发送流程,实现一键自动化。
感想体会
Python具有非常强大的库和开源的框架,但是在掌握了Python的基本语法之后有时并不能很好地利用这些库和框架,原因在于对函数的理解以及跨文件类的调用等不熟悉,以及不善于审阅代码和注释,不清楚其具体功能是什么,但是使用新库和新框架的探索过程还是非常有意思的,尤其当程序跑通之后会非常舒畅,同时感慨于某个库某个框架的强大。
善于学习和利用Python中的库和框架无疑会给程序设计和编写带来巨大的便宜和效率,今后要掌握对新库和新框架的学习方法。
课程总结
Python是面向对象的语言,不同于C语言的面向过程,这给我们带来一种全新的思路,即分门别类的思想,这样的思维模式其实是更有助于我们将某项任务进行拆分,或者可以说模块化,一定程度上可以提高程序设计的效率,在改动时也能很大程度上避免牵连到其他代码,并且Python有不断更新的、强大的库作为支撑,无疑可以让老练的程序员如虎添翼。
在整个课程的教学上逻辑也十分清晰,老师教学方式也比较轻松幽默,为我们打开了Python世界的大门,希望老师可以延续教学质量和教学方法,不断引领更多同学踏上Python坦途。
浙公网安备 33010602011771号