[Python] 纯文本查看 复制代码
# 导入模块
import requests
import parsel
import time
# 字符编码映射表(用于解密内容)
dit_data = {
'58670': '0',
'58413': '1',
'58678': '2',
'58371': '3',
'58353': '4',
'58480': '5',
'58359': '6',
'58449': '7',
'58540': '8',
'58692': '9',
'58712': 'a',
'58542': 'b',
'58575': 'c',
'58626': 'a',
'58691': 'e',
'58561': 'f',
'58362': 'g',
'58619': 'h',
'58430': 'i',
'58531': 'j',
'58588': 'k',
'58440': 'l',
'58681': 'm',
'58631': 'n',
'58376': 'o',
'58429': 'p',
'58555': 'q',
'58498': 'r',
'58518': 's',
'58453': 't',
'58397': 'u',
'58356': 'v',
'58435': 'w',
'58514': 'x',
'58482': 'y',
'58529': 'z',
'58515': 'A',
'58688': 'B',
'58709': 'C',
'58344': 'D',
'58656': 'E',
'58381': 'F',
'58576': 'G',
'58516': 'H',
'58463': 'I',
'58649': 'J',
'58571': 'K',
'58558': 'L',
'58433': 'M',
'58517': 'N',
'58387': 'O',
'58687': 'P',
'58537': 'Q',
'58541': 'R',
'58458': 'S',
'58390': 'T',
'58466': 'U',
'58386': 'V',
'58697': 'W',
'58519': 'X',
'58511': 'Y',
'58634': 'Z',
'58611': '的',
'58590': '一',
'58398': '是',
'58422': '了',
'58657': '我',
'58666': '不',
'58562': '人',
'58345': '在',
'58510': '他',
'58496': '有',
'58654': '这',
'58441': '个',
'58493': '上',
'58714': '们',
'58618': '来',
'58528': '到',
'58620': '时',
'58403': '大',
'58461': '地',
'58481': '为',
'58700': '子',
'58708': '中',
'58503': '你',
'58442': '说',
'58639': '生',
'58506': '国',
'58663': '年',
'58436': '着',
'58563': '就',
'58391': '那',
'58357': '和',
'58354': '要',
'58695': '她',
'58372': '出',
'58696': '也',
'58551': '得',
'58445': '里',
'58408': '后',
'58599': '自',
'58424': '以',
'58394': '会',
'58348': '家',
'58426': '可',
'58673': '下',
'58417': '而',
'58556': '过',
'58603': '天',
'58565': '去',
'58604': '能',
'58522': '对',
'58632': '小',
'58622': '多',
'58350': '然',
'58605': '于',
'58617': '心',
'58401': '学',
'58637': '么',
'58684': '之',
'58382': '都',
'58464': '好',
'58487': '看',
'58693': '起',
'58608': '发',
'58392': '当',
'58474': '没',
'58601': '成',
'58355': '只',
'58573': '如',
'58499': '事',
'58469': '把',
'58361': '还',
'58698': '用',
'58489': '第',
'58711': '样',
'58457': '道',
'58635': '想',
'58492': '作',
'58647': '种',
'58623': '开',
'58521': '美',
'58609': '总',
'58530': '从',
'58665': '无',
'58652': '情',
'58676': '己',
'58456': '面',
'58581': '最',
'58509': '女',
'58488': '但',
'58363': '现',
'58685': '前',
'58396': '些',
'58523': '所',
'58471': '同',
'58485': '日',
'58613': '手',
'58533': '又',
'58589': '行',
'58527': '意',
'58593': '动',
'58699': '方',
'58707': '期',
'58414': '它',
'58596': '头',
'58570': '经',
'58660': '长',
'58364': '儿',
'58526': '回',
'58501': '位',
'58638': '分',
'58404': '爱',
'58677': '老',
'58535': '因',
'58629': '很',
'58577': '给',
'58606': '名',
'58497': '法',
'58662': '间',
'58479': '斯',
'58532': '知',
'58380': '世',
'58385': '什',
'58405': '两',
'58644': '次',
'58578': '使',
'58505': '身',
'58564': '者',
'58412': '被',
'58686': '高',
'58624': '已',
'58667': '亲',
'58607': '其',
'58616': '进',
'58368': '此',
'58427': '话',
'58423': '常',
'58633': '与',
'58525': '活',
'58543': '正',
'58418': '感',
'58597': '见',
'58683': '明',
'58507': '问',
'58621': '力',
'58703': '理',
'58438': '尔',
'58536': '点',
'58384': '文',
'58484': '几',
'58539': '定',
'58554': '本',
'58421': '公',
'58347': '特',
'58569': '做',
'58710': '外',
'58574': '孩',
'58375': '相',
'58645': '西',
'58592': '果',
'58572': '走',
'58388': '将',
'58370': '月',
'58399': '十',
'58651': '实',
'58546': '向',
'58504': '声',
'58419': '车',
'58407': '全',
'58672': '信',
'58675': '重',
'58538': '三',
'58465': '机',
'58374': '工',
'58579': '物',
'58402': '气',
'58702': '每',
'58553': '并',
'58360': '别',
'58389': '真',
'58560': '打',
'58690': '太',
'58473': '新',
'58512': '比',
'58653': '才',
'58704': '便',
'58545': '夫',
'58641': '再',
'58475': '书',
'58583': '部',
'58472': '水',
'58478': '像',
'58664': '眼',
'58586': '等',
'58568': '体',
'58674': '却',
'58490': '加',
'58476': '电',
'58346': '主',
'58630': '界',
'58595': '门',
'58502': '利',
'58713': '海',
'58587': '受',
'58548': '听',
'58351': '表',
'58547': '德',
'58443': '少',
'58460': '克',
'58636': '代',
'58585': '员',
'58625': '许',
'58694': '陵',
'58428': '先',
'58640': '口',
'58628': '由',
'58612': '死',
'58446': '安',
'58468': '写',
'58410': '性',
'58508': '马',
'58594': '光',
'58483': '白',
'58544': '或',
'58495': '住',
'58450': '难',
'58643': '望',
'58486': '教',
'58406': '命',
'58447': '花',
'58669': '结',
'58415': '乐',
'58444': '色',
'58549': '更',
'58494': '拉',
'58409': '东',
'58658': '神',
'58557': '记',
'58602': '处',
'58559': '让',
'58610': '母',
'58513': '父',
'58500': '应',
'58378': '直',
'58680': '字',
'58352': '场',
'58383': '平',
'58454': '报',
'58671': '友',
'58668': '关',
'58452': '放',
'58627': '至',
'58400': '张',
'58455': '认',
'58416': '接',
'58552': '告',
'58614': '入',
'58582': '笑',
'58534': '内',
'58701': '英',
'58349': '军',
'58491': '候',
'58467': '民',
'58365': '岁',
'58598': '往',
'58425': '何',
'58462': '度',
'58420': '山',
'58661': '觉',
'58615': '路',
'58648': '带',
'58470': '万',
'58377': '男',
'58520': '边',
'58646': '风',
'58600': '解',
'58431': '叫',
'58715': '任',
'58524': '金',
'58439': '快',
'58566': '原',
'58477': '吃',
'58642': '妈',
'58437': '变',
'58411': '通',
'58451': '师',
'58395': '立',
'58369': '象',
'58706': '数',
'58705': '四',
'58379': '失',
'58567': '满',
'58373': '战',
'58448': '远',
'58659': '格',
'58434': '士',
'58679': '音',
'58432': '轻',
'58689': '目',
'58591': '条',
'58682': '呢',
}
# 模拟浏览器
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36'
}
# 打印版权和免责声明
print("\n\n \033[31m免责声明:\033[0m")
print(" 本程序仅用于学习和研究Python网络爬虫和网页处理技术")
print(" 本程序不得用于任何非法活动或侵犯他人权益的行为")
print(" 使用本程序所产生的一切法律责任和风险,均由用户自行承担")
print(" 作者不对因使用该程序而导致的任何损失或损害承担任何责任")
print("\n\n")
def search_and_download():
# 搜索功能
while True:
key = input('请输入书名或者作者名进行搜索:')
if key.strip(): # 检查是否为空
break
else:
print('\033[31m请输入有效的书名或作者名!\033[0m')
print('\n\033[31m正在搜索中,请稍等....\033[0m')
info = []
for i in range(3): # 假设每页显示10本书,最多搜索3页
search_url = f'https://novel.snssdk.com/api/novel/channel/homepage/search/search/v1/?device_platform=android&parent_enterfrom=novel_channel_search.tab&aid=1967&offset={i * 10}&q={key}'
try:
search_data = requests.get(url=search_url, headers=headers).json()
except requests.exceptions.RequestException as e:
print(f'搜索请求失败: {e}')
return # 返回搜索
if search_data['message'] != 'success':
print(search_data['message'])
continue
for book in search_data['data']['ret_data']:
if book['author'] not in ['番茄漫画', '番茄畅听']:
book_id = book['book_id']
# 获取章节数量
book_url = f"https://fanqienovel.com/page/{book_id}?enter_from=search"
try:
book_response = requests.get(book_url, headers=headers)
book_selector = parsel.Selector(book_response.text)
chapter_count = len(book_selector.css('.chapter-item'))
except requests.exceptions.RequestException as e:
print(f"获取章节数量失败: {e}")
continue # 跳过该书籍,继续搜索下一个
dit = {
'title': book['title'],
'author': book['author'],
'book_id': book_id,
'chapter_count': chapter_count
}
info.append(dit)
if not search_data['data']['has_more']:
break
# 判断搜索结果中是否有章节数量为0的书籍
all_zero_chapters = all(book['chapter_count'] == 0 for book in info)
# 展示搜索结果(可选)
print("\n搜索结果:")
for i, book in enumerate(info):
print(f"{i + 1}. {book['title']} - 作者:{book['author']} (共{book['chapter_count']}章) ID: {book['book_id']}")
if all_zero_chapters:
print("\033[31m所有搜索到的书籍都没有章节,网络请求受限,请更换IP!!!\033[0m")
return # 返回搜索
while True:
try:
selected_index = int(input('\n\033[31m请输入要下载的书籍序号\033[0m(输入0返回搜索):')) - 1 # 序号从1开始,数组从0开始
if selected_index == -1:
return # 返回搜索
elif selected_index >= 0 and selected_index < len(info):
break # 输入有效序号
else:
print("无效的序号!")
except ValueError:
print("请输入数字!")
# 获取小说名字和章节
while True: # 添加循环,如果获取小说信息失败,重新输入序号
try:
book_id = info[selected_index]['book_id']
url = f'https://fanqienovel.com/page/{book_id}?enter_from=search'
response = requests.get(url=url, headers=headers)
response.raise_for_status() # 检查请求是否成功
break # 获取小说信息成功,退出循环
except requests.exceptions.RequestException as e:
print(f'获取小说信息失败: {e}')
print('\033[31m该小说没有章节,请重新选择序号下载\033[0m')
while True:
try:
selected_index = int(input('\n\033[31m请输入要下载的书籍序号\033[0m(输入0返回搜索):')) - 1
if selected_index == -1:
return # 返回搜索
elif selected_index >= 0 and selected_index < len(info):
break
else:
print("无效的序号!")
except ValueError:
print("请输入数字!")
html_data = response.text
# 解析数据
selector = parsel.Selector(html_data)
# 小说名称(直接写入代码中)
name = selector.css('.info-name h1::text').get()
# 提取章节
chapter_items = selector.css('.chapter-item')
# 保存章节标题和链接的列表
title_href_list = []
# 遍历章节列表,提取章节标题和链接
for item in chapter_items:
title = item.css('.chapter-item-title::text').get()
href = item.css('.chapter-item-title::attr(href)').get()
title_href_list.append((title, href))
# 使用字典存储章节标题和链接,并使用章节标题作为键
chapter_dict = {title: href for title, href in title_href_list}
# 获取章节序号
chapter_order = list(chapter_dict.keys())
# 打印正在下载信息
print(f"\n==========\033[31m {name} - 作者:{book['author']},共 {len(chapter_order)} 章,ID: {book_id}\033[0m ==========\n")
# 开始下载时间
start_time = time.time()
# 遍历章节字典,按照章节序号下载
download_success = True
for i, title in enumerate(chapter_order, 1): # 从1开始计数
print(f'正在下载 {i}/{len(chapter_order)}: {title}')
href = chapter_dict[title]
link_url = 'https://fanqienovel.com' + href + '?enter_from=page'
try:
# 发送请求 获取数据
link_data = requests.get(url=link_url, headers=headers).text
# 解析数据
link_selector = parsel.Selector(link_data)
# 提取小说内容
content_list = link_selector.css('.muye-reader-content-16 p::text').getall()
# 列表合并字符串
content = '\n\n'.join(content_list)
# 字典解码
novel_content = ''
for index in content:
try:
word = dit_data[str(ord(index))]
except:
word = index
novel_content += word
with open(name + '.txt', mode='a', encoding='utf-8') as f:
f.write(title)
f.write('\n\n')
f.write(novel_content)
f.write('\n\n')
except requests.exceptions.RequestException as e:
print(f'获取章节内容失败: {e}')
download_success = False # 设置下载失败标志
# 结束下载时间
end_time = time.time()
# 计算下载耗时
download_duration = end_time - start_time
# 判断耗时是否超过一分钟
if download_duration < 60:
print(f'下载耗时: {download_duration:.2f} 秒')
else:
minutes = int(download_duration // 60)
seconds = int(download_duration % 60)
print(f'下载耗时: {minutes} 分 {seconds} 秒')
if download_success:
print(f'=============== {name} 下载完成,存放在软件目录里 ==========\n')
else:
print(f'=============== {name} 下载失败 ==========\n')
while True:
search_and_download()