????????书接上文,说到想批量下载电影,通过一个数据库获取了所有下载内容的链接,所以这个数据库从那里来呢?当然要依靠我们万能的python了,正好我最近在用一个bt站,又很想从上面找日剧看,奈何一个一个搜索下载太麻烦了,更不要说搭建好qbittorrent的webui,然后从nas下载了。
?
? ? ? ? ?基本架构:用python获取日剧信息,然后从中间获得清晰度最高的种子,然后传输到局域网的nas下载。
????????
? ? ? ? 先来看看爬虫的核心部分吧~后面有源码!
? ? ? ? 首先当然是打开搜索页面,发现即使按照日本+日语两个关键词搜索,仍然会有007这样的电影混进其中,因此需要改进,改进方法就是从html中抓取是否包含“语 ? ??言 日语”以及“地 ? ? 域 日本”这样的关键词。第一个部分的源代码如下:
import requests
import re
import time
import random
def getdetail(file, id):
"""
分析详情页函数
:param file: 详情页html文件对象
:return: 一个字典,包含id、译名、清晰度信息、其他版本id、带passkey的url,如果不是日剧就返回空
"""
with open('log.txt', 'r', encoding='utf8') as logfile:
logtext = logfile.read()
if logtext.find(f"'id': {str(id)}") != -1:
print('getted!:', id)
return
return_dic = {
'id': id,
'name': '',
'res': '',
'url': '',
'other': []
}
text = file.read()
if text.find('产 地 日本') == -1 or text.find('语 言 日语') == -1:
return
res_re = re.compile('<b>分辨率: </b>(.*?)[pi]')
try:
return_dic['res'] = int(res_re.findall(text)[0])
except:
return_dic['res'] = 0
other_re = re.compile('a href="https://www\.hdarea\.co/details\.php\?id=(.*?)&hit=1"')
return_dic['other'] = other_re.findall(text)
name_re = re.compile('译 名 (.*?)<')
name_list = name_re.findall(text)
if name_list:
if name_list[0].find('/') != -1:
return_dic['name'] = name_list[0][:name_list[0].find('/')]
else:
return_dic['name'] = name_list[0]
else:
bad_name = re.compile('">副标题</td><td class="rowfollow" valign="top" align="left">(.*?)</td></tr>').findall(text)[0]
return_dic['name'] = bad_name[:bad_name.find('/')]
return_dic['url'] = f'https://www.hdarea.co/download.php?id={str(id)}&passkey=你的passkey~(还想看我的??)'
print(return_dic)
with open('log.txt', 'a+') as logfile:
logfile.write(str(return_dic)+'\n')
????????下面详细介绍一下代码的每个部分在干什么:
? ? ? ? 输入一个html文件读取后的文件对象,以及这个页面对应的id
????????首先定义了一个return_dic的格式,从详情页分析会拿到id、译名、清晰度信息、其他版本id、带passkey的url,如果不是日剧就返回空
? ? ? ? 所以第一步自然是用刚刚订好的筛选条件看看是不是日剧,不是就可以下一个文件了。
? ? ? ? 第二步是用正则表达式尝试匹配一下清晰度,考虑到同时有1080p和1080i,咱们都要匹配上,如果不是按照这样格式写的(反正也不是很多),就给个0吧。随后调试过程中还发现,有一些格式写得很奔放,咱也看不懂但是就是不能这么比,但是会让我们的程序报错,那就给个try吧。
? ? ? ? 之后就是简单地字符串处理,用正则表达式匹配译名等信息(如果译名匹配不到就直接拿它的副标题),用来当作文件夹的名字,随后观察下载的url那一行,把相应的url给拼接好【我也是刚刚知道bt站给的这种https链接中决定下载的文件是什么的唯一凭证居然是那个唯一的id】
? ? ? ? 最后把它写到一个log里面,后续需要批量加入到qbittorrent,只需要连接好webui直接用这个log文件就可以了。
? ? ? ?
? ? ? ? 这段代码等于是整个工作的第二个部分,完成之后还有两个工作:第一个是获取这个getdetail函数中所需要的html文件,可以爬虫一下,然后抓取所有类似于链接的url,提取中间的id方便通信,第二个是读取log.txt,然后加入合适的量下载(一次下多了就怕分享率炸了),但是剩下的没有什么太多技术含量,所以就直接把所有文件的源码摆上来,之后再慢慢聊:
# hdarea_detail.py
import requests
import re
import time
import random
def getdetail(file, id):
"""
分析详情页函数
:param file: 详情页html文件对象
:return: 一个字典,包含id、译名、清晰度信息、其他版本id、带passkey的url,如果不是日剧就返回空
"""
with open('log.txt', 'r', encoding='utf8') as logfile:
logtext = logfile.read()
if logtext.find(f"'id': {str(id)}") != -1:
print('getted!:', id)
return
return_dic = {
'id': id,
'name': '',
'res': '',
'url': '',
'other': []
}
text = file.read()
if text.find('产 地 日本') == -1 or text.find('语 言 日语') == -1:
return
res_re = re.compile('<b>分辨率: </b>(.*?)[pi]')
try:
return_dic['res'] = int(res_re.findall(text)[0])
except:
return_dic['res'] = 0
other_re = re.compile('a href="https://www\.hdarea\.co/details\.php\?id=(.*?)&hit=1"')
return_dic['other'] = other_re.findall(text)
name_re = re.compile('译 名 (.*?)<')
name_list = name_re.findall(text)
if name_list:
if name_list[0].find('/') != -1:
return_dic['name'] = name_list[0][:name_list[0].find('/')]
else:
return_dic['name'] = name_list[0]
else:
bad_name = re.compile('">副标题</td><td class="rowfollow" valign="top" align="left">(.*?)</td></tr>').findall(text)[0]
return_dic['name'] = bad_name[:bad_name.find('/')]
return_dic['url'] = f'https://www.hdarea.co/download.php?id={str(id)}&passkey=你自己的id~~~~~~~~'
print(return_dic)
with open('log.txt', 'a+', encoding='utf8') as logfile:
logfile.write(str(return_dic)+'\n')
# print(text)
def random_sleep(length):
real_length = length + random.randint(-1000,1000) / 1000
time.sleep(real_length)
if __name__ == '__main__':
path = ''
with open(path+'detail.html', encoding='utf8') as file:
getdetail(file, 51665)
# hdarea_search.py
import requests
import re
import time
import random
from hdarea_detail import getdetail, random_sleep
def get_urls(file):
"""
输入搜索页file对象,返回所有下载url的id
:param file:
:return:
"""
return_list = []
text = file.read()
urls_re = re.compile('<a href="details\.php\?id=(.*?)&hit=1&')
urls_re.findall(text)
urls_re_return = urls_re.findall(text)
for url in urls_re_return:
if url not in return_list:
return_list.append(url)
print(return_list)
print(len(return_list))
return return_list
def get_all_info():
header = {
'Cookie': '你自己的cookie',
'authority': 'www.hdarea.co',
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.2 Safari/605.1.15'
}
path = ''
with open(path+'search.html', 'r', encoding='utf8') as file:
urls_list = get_urls(file)
for id in urls_list:
url = f'https://www.hdarea.co/details.php?id={str(id)}&hit=1'
page = requests.get(url, headers=header)
with open('detail.html', 'w', encoding='utf8') as detailfile:
detailfile.write(page.text)
with open('detail.html', 'r', encoding='utf8') as file:
getdetail(file, int(id))
random_sleep(1)
if __name__ == '__main__':
get_all_info()
# hdarea.py
import requests
from hdarea_search import get_all_info
from hdarea_detail import random_sleep
header = {
'Cookie': '你的cookie',
'authority': 'www.hdarea.co',
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.2 Safari/605.1.15'
}
for i in range(0, 36):
url = f'你在该站点搜索需要的内容的时候上面的url'
page = requests.get(url, headers=header)
print(url)
with open('search.html', 'w', encoding='utf8') as detailfile:
detailfile.write(page.text)
get_all_info()
random_sleep(5)
# hdarea_qbittorrent.py
from qbittorrent import Client
import time
def connect_to_qbit():
qb = Client('你的qbittorrent地址')
qb.login('你的账号', '你的密码')
# not required when 'Bypass from localhost' setting is active.
# defaults to admin:admin.
# to use defaults, just do qb.login()
torrents = qb.torrents()
return qb, torrents
def get_biggest(log_dic, id_num):
"""
:param log_dic: 处理完成的id:信息 字典
:param id_num: 待查找的id
:return: 返回同类文件中清晰度最高的id
"""
if not log_dic[id_num]['other']:
return id_num
else:
biggest_res = -1
return_id_num = 0
for other_id_num in log_dic[id_num]['other']:
try:
if log_dic[other_id_num]['res'] > biggest_res:
return_id_num = other_id_num
except:
pass
return return_id_num
def log_file_init():
"""
:return:读取log.txt文件并将内容初始化成为id:内容的字典
"""
log_list = []
with open('log.txt', 'r', encoding='utf8') as file:
lines = file.readlines()
for line in lines:
log_list.append(eval(line))
log_dic = {}
for log in log_list:
log_dic.update({log['id']: log})
return log_dic
# 读入log文件,并将id作为字典的key,信息作为字典的值
def qbit_download(qb, log_keyarg, id_num):
"""
:param qb: qb的client对象
:param log_keyarg: 记录信息的字典
:param id_num: 信息的id
:return: no
"""
savepath = f'/share/CACHEDEV4_DATA/movie/{log_keyarg["name"]}' # 这是我希望的在nas内的存储位置
qb.download_from_link(log_keyarg['url'], savepath=savepath)
localtime = time.asctime(time.localtime(time.time()))
with open('dl_log.txt', 'a', encoding='utf8') as dl_log_file:
dl_log_file.write(str(id_num) + str(localtime) + '\n')
print(f'download:{id_num}, time:{localtime},')
def qbit_main(start=0, end=0):
qb, torrents = connect_to_qbit()
log_dic = log_file_init()
s = start
for id_num in log_dic.keys():
if id_num == get_biggest(log_dic, id_num): # 这条就是最清楚的,可以下载!
qbit_download(qb, log_dic[id_num], id_num)
s += 1
print(f'第{s}条任务:', log_dic[id_num])
if s == end:
break
if __name__ == '__main__':
# qb, torrents = connect_to_qbit()
qbit_main(0, 40)
每个文件实现的功能:
hdarea.py | 项目的最终main文件,在获得了log之后会控制直接读取log内的内容并将下载任务传到qbit的网页端 | hdarea_search.py | 实现从搜索页提取详情页的功能,简单分析页面就可以写出来 | hdarea_detail.py | 实现从详情页提取关键信息并输出到log文件的过程 | hdarea_qbittorrent.py | 实现将下载任务传到qbit的网页端的过程 |
值得注意的是源代码中我隐藏了部分信息:
????????一个是cookie,读者们注册完bt站之后访问可以从开发者选项中看到本次请求的cookie,把自己的cookie换过来就好,注意不要被其他人看到啦~
????????另外一个就是torrent站的passkey,这个非常非常重要!它是torrent站识别身份的唯一信息,也就是说是谁在上传下载,纯靠这个passkey判定,如果被别人知道了,别人大概会拿你的账户猛下东西,你如果没来得及重制passkey,你大概就可以准备新账号了。
? ? ? ? 第三个是看似没有那么重要的qbit的webui的账号密码,别人最多使用你的qbit客户端,但是之前有通过qbit客户端入侵机器锁定文件然后勒索的先例,也请大家注意不要泄露出去啦~
如果有源码使用和优化、以及其余问题欢迎直接在评论区交流~
封面
|