新知:
输出:
给 kakaopage 的等就免费的漫画,做了个自动检测等待券和使用脚本。
import requests
import time
import datetime
import pytz
import json
# --- 配置区 ---
# 监控漫画ID
CONTENT_ID = '3577'
COOKIE = ''
# 全局请求头
HEADERS = {
'accept': 'application/json, text/plain, */*',
'accept-language': 'ko',
'origin': 'https://webtoon.kakao.com',
'referer': 'https://webtoon.kakao.com/',
'sec-ch-ua': '"Chromium";v="140", "Not=A?Brand";v="24", "Google Chrome";v="140"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"Windows"',
'sec-fetch-dest': 'empty',
'sec-fetch-mode': 'cors',
'sec-fetch-site': 'same-site',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36',
'cookie': COOKIE
}
# API 模块
TICKET_STATUS_URL = f'https://gateway-kw.kakao.com/ticket/v2/views/content-home/available-tickets?contentId={CONTENT_ID}'
EPISODES_LIST_URL = f'https://gateway-kw.kakao.com/episode/v2/views/content-home/contents/{CONTENT_ID}/episodes?sort=-NO&offset=0&limit=1000'
PURCHASE_URL_TEMPLATE = 'https://gateway-kw.kakao.com/episode/v3/episodes/{episode_id}/pass'
def get_current_time_str():
"""获取格式化的当前时间字符串"""
return datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
def check_ticket_status():
"""查询免费券状态"""
try:
print(f"[{get_current_time_str()}] 正在检查免费券状态...")
response = requests.get(TICKET_STATUS_URL, headers=HEADERS)
response.raise_for_status() # 如果请求失败 (例如 401, 403), 会抛出异常
return response.json()
except requests.exceptions.RequestException as e:
print(f" [错误] 检查免费券状态失败: {e}")
if "401" in str(e) or "403" in str(e):
print(" [提示] 可能是Cookie失效了,请更新脚本中的COOKIE变量。")
return None
def get_episodes():
"""获取所有章节列表"""
try:
print(f"[{get_current_time_str()}] 正在获取章节列表...")
response = requests.get(EPISODES_LIST_URL, headers=HEADERS)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
print(f" [错误] 获取章节列表失败: {e}")
return None
def purchase_episode(episode_id):
"""使用免费券购买指定章节"""
purchase_url = PURCHASE_URL_TEMPLATE.format(episode_id=episode_id)
payload = {"ticketType": "wait_for_free"}
purchase_headers = HEADERS.copy()
purchase_headers['content-type'] = 'application/json;charset=UTF-8'
try:
print(f"[{get_current_time_str()}] 正在尝试使用免费券解锁章节 (ID: {episode_id})...")
response = requests.post(purchase_url, headers=purchase_headers, data=json.dumps(payload))
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
print(f" [错误] 解锁章节失败: {e}")
return None
def find_next_episode_to_unlock(episodes_data):
"""从章节列表中找到下一个需要解锁的章节"""
if not episodes_data or 'data' not in episodes_data or 'episodes' not in episodes_data['data']:
return None
# 按章节号 'no' 从小到大排序
sorted_episodes = sorted(
episodes_data['data']['episodes'],
key=lambda x: int(x['no']) if str(x['no']).isdigit() else x['no']
)
for episode in sorted_episodes:
# 已经能读的章节直接跳过
if episode.get('readable', False):
continue
if episode.get('useType') == 'WAIT_FOR_FREE':
return episode
return None
def main():
"""主函数"""
print("="*40)
print("漫画自动解锁脚本已启动")
print(f"目标漫画ID: {CONTENT_ID}")
print("="*40)
while True:
status_data = check_ticket_status()
if not status_data:
print(f" 将在10分钟后重试...")
time.sleep(600) # 如果请求失败,等待10分钟再试
continue
ticket_count = status_data.get('data', {}).get('ticketCount', 0)
if ticket_count > 0:
print(f" [成功] 发现 {ticket_count} 张可用免费券!")
episodes_data = get_episodes()
if episodes_data:
next_episode = find_next_episode_to_unlock(episodes_data)
if next_episode:
episode_id = next_episode['id']
episode_no = next_episode['no']
episode_title = next_episode['title']
print(f" [发现] 下一个要解锁的章节是: 第 {episode_no} 话 - {episode_title}")
purchase_result = purchase_episode(episode_id)
if purchase_result and purchase_result.get('data', {}).get('pass') is True:
print(f" [成功] 章节 '{episode_title}' 解锁成功!")
else:
print(f" [失败] 章节 '{episode_title}' 解锁失败。服务器返回: {purchase_result}")
else:
print(" [完成] 所有可免费解锁的章节均已解锁,脚本将退出。")
break # 退出循环
# 无论成功与否,都等待一小段时间再检查,避免过快消耗掉所有券(如果有多张)
print(" 等待10秒后进行下一次检查...")
time.sleep(10)
else:
# 没有可用的券,计算等待时间
wait_for_free_info = status_data.get('data', {}).get('waitForFree')
if wait_for_free_info and 'rechargesDateTime' in wait_for_free_info:
recharge_time_str = wait_for_free_info['rechargesDateTime']
# 将ISO 8601格式的UTC时间字符串转换为datetime对象
recharge_time_utc = datetime.datetime.fromisoformat(recharge_time_str.replace('Z', '+00:00'))
# 转换为北京时间 (UTC+8)
tz_beijing = pytz.timezone("Asia/Shanghai")
recharge_time_local = recharge_time_utc.astimezone(tz_beijing)
# 获取当前的UTC时间
now_utc = datetime.datetime.now(pytz.utc)
wait_seconds = (recharge_time_utc - now_utc).total_seconds()
if wait_seconds > 0:
sleep_duration = wait_seconds + 60
wait_minutes = sleep_duration / 60
print(f" [等待] 当前没有免费券。下一张券将在 {recharge_time_local.strftime('%Y-%m-%d %H:%M:%S %Z')} 发放。")
print(f" 脚本将休眠约 {int(wait_minutes)} 分钟...")
time.sleep(sleep_duration)
else:
# 如果计算出的时间在过去,说明可能刚发放,等待1分钟再检查
print(" [提示] 下一张券的刷新时间已过,可能正在发放。等待1分钟后重新检查。")
time.sleep(60)
else:
print(" [错误] 无法获取下一张券的时间信息。将在10分钟后重试。")
time.sleep(600)
print("-" * 20)
if __name__ == "__main__":
main()
杂谈:
听说 731 很难看,看完之后发现是喜剧片。