卓越飞翔博客卓越飞翔博客

卓越飞翔 - 您值得收藏的技术分享站
技术文章30769本站已运行3725

Python 追剧助手from IMDB

1. 前言
今年是AI元年,自Chat GPT之后,国内各大互联网公司也推出了自家的AI大语言模型。我连Python编程的函数和语法都没入门,日常也就使用VBA辅助来处理工作中的重复事项,所以今天分享的代码完全来自于AI所写。
从上学的时候养成了看美剧的习惯,入门美剧是超人前传和神盾局特工。以前Series Guide还能正常使用的时候,一直用来记录追剧进度,后来也用过人人开发的软件。但是到现在就一直没有心水的软件,要么就是功能对于我来说不够轻量,要么就是用不上的功能太多。所以一直是在便签中记录。乘着AI的风,索性奴役它来为我服务一次。

2. 软件对比
这次写追剧助手前,分别试用了文心一言和通义千问一段时间,过程中也发现两家的模型各有优劣,下面就我个人使用体验分享一下。

对比项目    文心一言    通义千问
数据同步    多端同步,相互可以打开记录    网页和app无法互通
软件体验    有社区性质,安装包较大    功能专注,安装包小
上下文理解    有时候会忘了之前的对话,连续性差点,对话的时候直接提示前文会改善    连续性较好,无提示也能连续
代码质量    注释和代码常混到一行,需手动换行    注释和代码完美分开
对话字符限制    网页和app均为2000字符    网页10000字符,app 2000字符
3. 正文代码
库的导入。在代码的前六行是引用了Python库,官方库安装速度较慢,甚至有时候连不上,所以安装时可以使用镜像源代替,比如清华源。
安装单个库:
pip install re -i https://pypi.tuna.tsinghua.edu.cn/simple
安装多个库:
pip install bs4 datetime requests wcwidth pytz -i https://pypi.tuna.tsinghua.edu.cn/simple
程序使用方法:
代码第一段tv_shows字典存储的是每部美剧的信息,包括名称、IMDB ID和已观看剧集。
修改tv_show字典中的内容,watched_episodes的格式可是是S01E01也可以写成S1E1,但是必须是大写。新剧可以写成S01E00形式。
完整代码:
import requests
import re
from bs4 import BeautifulSoup
from datetime import datetime
import wcwidth
from pytz import timezone

# 创建一个字典来存储美剧信息
tv_shows = {
    "IronHeart钢铁之心": {"imdb_id": "tt13623126", "watched_episodes": "S01E00"},
    "Loki洛基": {"imdb_id": "tt9140554", "watched_episodes": "S02E06"},
    "What If假如": {"imdb_id": "tt10168312", "watched_episodes": "S02E03"},
    "Halo光环": {"imdb_id": "tt2934286", "watched_episodes": "S01E09"},
    "Superman & Lois超人与露易丝": {"imdb_id": "tt11192306", "watched_episodes": "S03E13"},
    "The Boys黑袍纠察队": {"imdb_id": "tt1190634", "watched_episodes": "S03E08"},
    "3 Body Problem三体": {"imdb_id": "tt13016388", "watched_episodes": "S01E00"}
}

def get_episode_info(imdb_id, watched_episodes):
    # 构造请求URL,用于获取指定IMDb ID的剧集信息
    episode_url = f"https://www.imdb.com/title/{imdb_id}/episodes"

    # 设置请求头,模拟浏览器发送请求
    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.3'
    }

    # 发送请求并获取响应
    response = requests.get(episode_url, headers=headers)

    # 检查请求是否成功(状态码为200表示成功)
    if response.status_code == 200:
        # 使用BeautifulSoup解析HTML响应
        soup = BeautifulSoup(response.text, 'html.parser')

        # 从HTML中提取所有季节编号
        episode_elements = soup.select('.ipc-tab')

        # 创建一个新的字典来存储季节编号
        print("\n")
        season_numbers = {}
        for element in episode_elements:
            html_fragment = str(element)
            matches = re.findall(r'<li.*?>(\d+)</li>', html_fragment)
            if matches:
                for season_number in matches:
                    season_numbers[season_number] = None  # 这里你可以根据需要存储其他相关信息
                    print(f"{show}的季: {season_number}")

        # 初始化一个列表,用于存储剧集信息
        episode_infos = []
        next_episode_dates_obj = {}  # 新增:用于存储每部剧下一次剧集更新日期
        next_episode_dates_str = {}

        # 打印表格标题
        print(f"Show\t\tIMDb ID\t\tWatched\t\tEpisodes\tRelease Date")

        # 标记是否已经找到已观看的剧集
        found_watched_episode = False

        # 遍历所有季节
        for season_number in sorted(season_numbers.keys(), reverse=True):
            if found_watched_episode:
                break

            # 构造请求URL,用于获取指定季节的剧集信息
            season_episode_url = f"https://www.imdb.com/title/{imdb_id}/episodes/?season={season_number}"

            # 发送请求并获取响应
            season_response = requests.get(season_episode_url, headers=headers)

            # 检查请求是否成功(状态码为200表示成功)
            if season_response.status_code == 200:
                # 使用BeautifulSoup解析HTML响应
                season_soup = BeautifulSoup(season_response.text, 'html.parser')

                # 从HTML中提取所有剧集的日期和编号
                episodes_dates = season_soup.select('article > div > div > div > div > span')
                episodes = season_soup.select('article > div > div > div > div > h4 > div > a > div')

                # 遍历所有剧集
                for episode_date, episode in zip(episodes_dates, episodes):
                    html_fragment = str(episode)
                    matches = re.findall(r'S(\d+)\.E(\d+)', html_fragment)
                    if matches:
                        season_number_str, episode_number_str = matches[0]
                        season_number = int(season_number_str)
                        episode_number = int(episode_number_str)

                        # 提取已观看剧集的季节和编号
                        watched_season, watched_episode = watched_episodes.replace("S", "").replace("E", ".").split('.')
                        watched_season_num = int(watched_season)
                        watched_episode_num = int(watched_episode)

                        # 判断当前剧集是否在已观看剧集之后
                        if watched_season_num < season_number or (watched_season_num == season_number and watched_episode_num < episode_number):
                            episode_date_text = episode_date.text.strip()

                            try:
                                # 将剧集日期字符串转换为日期对象(假设日期是美国洛杉矶时间)
                                episode_date_obj = datetime.strptime(episode_date_text, "%a, %b %d, %Y")

                                # 将美国洛杉矶时间转换为UTC时间
                                episode_date_utc = episode_date_obj.astimezone(timezone('US/Pacific'))

                                # 将UTC时间转换为北京时间
                                episode_date_bj = episode_date_utc.astimezone(timezone('Asia/Shanghai'))

                                date_str = episode_date_bj.strftime("%Y-%m-%d")

                                # 获取当前日期对象(北京时间)
                                current_date_obj = datetime.now(timezone('Asia/Shanghai'))

                                # 判断剧集日期是否早于当前日期
                                if episode_date_bj < current_date_obj:
                                    episode_info = {
                                        'show': show,
                                        'imdb_id': imdb_id,
                                        'watched_episodes': watched_episodes,
                                        'episode_number': f"S{season_number}E{episode_number}",
                                        'episode_date': date_str
                                    }

                                    # 将剧集信息添加到列表中
                                    episode_infos.append(episode_info)

                                    # 打印剧集信息
                                    print(f"{show}\t{imdb_id}\t{watched_episodes}\t\tS{season_number}E{episode_number}\t\t{date_str}")

                                else:
                                    # 如果剧集日期在今天之后,添加到next_episode_dates字典中
                                    if show not in next_episode_dates_obj or episode_date_bj < next_episode_dates_obj[show]:
                                        next_episode_dates_obj[show] = episode_date_bj
                                        next_episode_dates_str[show] = episode_date_bj.strftime("%Y-%m-%d")
                            except ValueError:
                                # 如果无法解析日期或仅包含年份,跳过此剧集
                                pass

                        # 如果找到了已观看的剧集,停止循环
                        if watched_season_num > season_number:# and watched_episode_num > episode_number:
                            found_watched_episode = True
                            break

            else:
                # 如果请求失败,打印错误信息
                print(f"请求失败,状态码:{season_response.status_code}")

        return episode_infos, next_episode_dates_str
    else:
        # 如果请求失败,打印错误信息
        print(f"请求失败,状态码:{response.status_code}")
        return None, {}

# 获取并更新剧集信息
all_episode_infos = {}
next_episode_dates_for_shows = {}  # 修改:创建一个新字典来存储每个剧集的下一次更新日期
for show in tv_shows.keys():
    episode_infos, next_episode_dates_str = get_episode_info(tv_shows[show]["imdb_id"], tv_shows[show]["watched_episodes"])
    if episode_infos is not None:
        all_episode_infos[show] = episode_infos

    if show in next_episode_dates_str:
        next_episode_dates_for_shows[show] =next_episode_dates_str[show] # 存储下一次更新日期

    else:
        next_episode_dates_for_shows[show] = "未知"

# 计算所有剧名的最大宽度(考虑多字节字符)
max_show_width = max(sum(wcwidth.wcwidth(c) for c in show) for show in tv_shows.keys())

print('\n\n\n追剧进度: ')
print(f"{'Show':<{max_show_width}}\tWatched\t\tStatus\t\tNext Release")

# 统计展示
for show in tv_shows.keys():
    if show in all_episode_infos:
        unwatched_episodes = len(all_episode_infos[show])
        if unwatched_episodes>0:
            status = f"更新{unwatched_episodes}集"
        else:
            status = "未更新"

    if show in next_episode_dates_for_shows:
        next_episode_date_str = next_episode_dates_for_shows[show]
    else:
        next_episode_date_str = "无该剧" 

    # 计算剧名的填充空格数量
    padding = ' ' * (max_show_width - sum(wcwidth.wcwidth(c) for c in show))

    # 打印剧集统计信息
    print(f"{show}{padding}\t{tv_shows[show]['watched_episodes']}\t\t{status}\t\t{next_episode_date_str}")
4. 安卓运行
写助手的初衷也是多平台都可以通用,PC端运行Python程序比较方便,安卓平台目前使用了Pydroid 3运行程序。Pydroid 3目前安装版本为5.00,有需要可以从下面的链接自取。
下载地址:https://www.alipan.com/s/trsJpuRV9uB
提取码: 8lq0
运行结果参考:
IronHeart钢铁之心的季: 1
Show            IMDb ID         Watched         Episodes        Release Date

Loki洛基的季: 1
Loki洛基的季: 2
Show            IMDb ID         Watched         Episodes        Release Date

What If假如的季: 1
What If假如的季: 2
What If假如的季: 3
Show            IMDb ID         Watched         Episodes        Release Date
What If假如     tt10168312      S02E03          S2E4            2023-12-25
What If假如     tt10168312      S02E03          S2E5            2023-12-26
What If假如     tt10168312      S02E03          S2E6            2023-12-27
What If假如     tt10168312      S02E03          S2E7            2023-12-28
What If假如     tt10168312      S02E03          S2E8            2023-12-29
What If假如     tt10168312      S02E03          S2E9            2023-12-30

Halo光环的季: 1
Halo光环的季: 2
Show            IMDb ID         Watched         Episodes        Release Date

Superman & Lois超人与露易丝的季: 1
Superman & Lois超人与露易丝的季: 2
Superman & Lois超人与露易丝的季: 3
Superman & Lois超人与露易丝的季: 4
Show            IMDb ID         Watched         Episodes        Release Date

The Boys黑袍纠察队的季: 1
The Boys黑袍纠察队的季: 2
The Boys黑袍纠察队的季: 3
The Boys黑袍纠察队的季: 4
Show            IMDb ID         Watched         Episodes        Release Date

3 Body Problem三体的季: 1
Show            IMDb ID         Watched         Episodes        Release Date

追剧进度:
Show                            Watched         Status          Next Release
IronHeart钢铁之心               S01E00          未更新          未知
Loki洛基                        S02E06          未更新          未知
What If假如                     S02E03          更新6集         未知
Halo光环                        S01E09          未更新          2024-02-08
Superman & Lois超人与露易丝     S03E13          未更新          未知
The Boys黑袍纠察队              S03E08          未更新          未知
3 Body Problem三体              S01E00          未更新          2024-03-21
最后的追剧进度在控制台打印出来是对齐的,因为控制台是等宽字体。

最后一列展示今天以后下一集的播放日期,是奴役了通义千问两个晚上,再加上自己修改才搞定的,通义千问始终没能写出来。

5. 后记
Q:为什么没有采用多线程或者异步请求加快程序运行速度?
A:首先是技术限制,作为Python小白我不会写,即便是让通义千问写出来,我也不知道是否适用。
其次是网络上一直提爬网站容易封IP,所以虽然运行速度慢,但作为自用也足够了,比上网站一部一部查快多了。
Q:程序能否长期稳定使用?
A:取决于IMDB网站结构是否变化,或者对爬取网站有了什么新的要求。
总的来说,个人认为大语言模型的推出比什么元宇宙融合概念更具有跨时代的意义,也让技术小白在娱乐和工作中更加自如。搜索效率比百度等网页搜索更高(语言类);让不懂编程的人也可以把自己的想法变成现实;以前写文章材料,需要绞尽脑汁去用优雅的语言润色,现在甩给AI就可以得到答案。总的来说,搞文字它是在行的。
不过它们的出现是不是会限制个人的思考能力和再学习能力,相信最终时间会给出答案。
卓越飞翔博客
上一篇: Python 一个壁纸爬虫脚本
下一篇: 返回列表
留言与评论(共有 0 条评论)
   
验证码:
隐藏边栏