Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

增加输入用户id批量下载功能,加入cookie,数据库增加id对应中文名title #9

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,12 @@
<td align="center">是否将每个作品的文件储存至单独的文件夹;文件夹名称与文件名称保持一致</td>
<td align="center">false</td>
</tr>
<tr>
<td align="center">cookie</td>
<td align="center">str</td>
<td align="center">增加cookie可以避免快手弹出验证码导致链接获取失败问题</td>
<td align="center">false</td>
</tr>
</tbody>
</table>

Expand Down
20 changes: 19 additions & 1 deletion source/app/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
DISCLAIMER_TEXT,
)
from source.downloader import Downloader
from source.downloader.kuaishou import Kuaishou
from source.extract import APIExtractor
from source.extract import PageExtractor
from source.link import DetailPage
Expand Down Expand Up @@ -93,7 +94,23 @@ async def __detail_enquire(self):
self.running = False
break
await self.detail(text)

async def __detail_uservideo(self):
while self.running:
text = self.console.input("请输入用户快手作品链接:")
if not text:
break
downloader = Kuaishou(
cookie="kpf=PC_WEB; clientid=3; did=web_eda1a93f1f2a99778b3dea2ed60fc496; arp_scroll_position=0; userId=3568785353; kpn=KUAISHOU_VISION; kuaishou.server.web_st=ChZrdWFpc2hvdS5zZXJ2ZXIud2ViLnN0EqABudUVe1aunIWZSj6PE3uzs67cNzH-u5iyM44cD-NrZMfYX3CxZhlerbs70kawwFNxwO_THqC0Nf4gJX8NLg72iiXDKhf2WyPRNrL-JplI6wpbEMT_hQld8muKZD679iFrkGtXHiCA4391rkmAE2COAE8E2wf6_mY43fH7Ccbbaqks3K1hBdx62P-xvWbRjj0714LEf09TPgN-W8BkJeNdgxoStEyT9S95saEmiR8Dg-bb1DKRIiBsiinsiplsExnD2Hh-ZL1z_pdtWpKi_aIQWjmGfN17MigFMAE; kuaishou.server.web_ph=ad0f7ffe552ff2aa87ae7270235d15d81d32",
user_id=text,
time_min=None,
time_max=None,
)
videos = downloader.getVideos()
for index, video in enumerate(videos):
videourl = "https://www.kuaishou.com/short-video/%s" % video['video_id']
# print(video['video_id'])
print(videourl)
await self.detail(videourl)
async def __main_menu(self):
while self.running:
self.__update_menu()
Expand All @@ -111,6 +128,7 @@ async def __main_menu(self):
def __update_menu(self):
self.__function = (
("批量下载快手作品", self.__detail_enquire),
("批量下载快手用户作品", self.__detail_uservideo),
(f"{self.MENU_TIP[self.config["Update"]]}检查更新功能", self.__modify_update),
(f"{self.MENU_TIP[self.config["Record"]]}下载记录功能", self.__modify_record),
)
Expand Down
6 changes: 3 additions & 3 deletions source/config/parameter.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class Parameter:
def __init__(self,
console: "ColorConsole",
cleaner: "Cleaner",
# cookie: str,
cookie: str = "kpf=PC_WEB; clientid=3; did=web_eda1a93f1f2a99778b3dea2ed60fc496; arp_scroll_position=0; userId=3568785353; kpn=KUAISHOU_VISION; kuaishou.server.web_st=ChZrdWFpc2hvdS5zZXJ2ZXIud2ViLnN0EqABudUVe1aunIWZSj6PE3uzs67cNzH-u5iyM44cD-NrZMfYX3CxZhlerbs70kawwFNxwO_THqC0Nf4gJX8NLg72iiXDKhf2WyPRNrL-JplI6wpbEMT_hQld8muKZD679iFrkGtXHiCA4391rkmAE2COAE8E2wf6_mY43fH7Ccbbaqks3K1hBdx62P-xvWbRjj0714LEf09TPgN-W8BkJeNdgxoStEyT9S95saEmiR8Dg-bb1DKRIiBsiinsiplsExnD2Hh-ZL1z_pdtWpKi_aIQWjmGfN17MigFMAE; kuaishou.server.web_ph=ad0f7ffe552ff2aa87ae7270235d15d81d32",
folder_name: str = "Download",
work_path: str = "",
timeout=TIMEOUT,
Expand All @@ -39,7 +39,7 @@ def __init__(self,
self.proxy = proxy
self.folder_name = self.__check_folder_name(folder_name)
self.work_path = self.__check_work_path(work_path)
# self.cookie = self.__check_cookie(cookie)
self.cookie = self.__check_cookie(cookie)
self.cover = self.__check_cover(cover)
self.music = self.check_bool(music, False)
self.download_record = self.check_bool(download_record, True)
Expand All @@ -58,7 +58,7 @@ def run(self) -> dict:
"proxy": self.proxy,
"work_path": self.work_path,
"folder_name": self.folder_name,
# "cookie": self.cookie,
"cookie": self.cookie,
"cover": self.cover,
"music": self.music,
"download_record": self.download_record,
Expand Down
4 changes: 3 additions & 1 deletion source/downloader/downloader.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ async def __handle_detail(self, data: list[dict], app: bool, ):
for item in data:
if await self.database.has_download_data(i := item["detailID"]):
self.console.info(f"作品 {i} 存在下载记录,跳过下载!")
await self.database.update_download_data(i, i := item["caption"])

continue
name = self.__generate_name(item, app, )
match item["photoType"]:
Expand Down Expand Up @@ -183,7 +185,7 @@ async def __download_file(self, url: str, path: "Path", progress: Progress, id_:
return False
self.move(temp, path)
self.console.info(f"【{tip}】{truncation(path.name)} 下载完成")
await self.database.write_download_data(id_)
await self.database.write_download_data(id_,path.name)
return True

def __extract_type(self, content: str) -> str:
Expand Down
208 changes: 208 additions & 0 deletions source/downloader/kuaishou.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
# coding=utf-8
import requests
import time
import os
import re

class Kuaishou:

def __init__(self, cookie, user_id=None, time_min=None, time_max=None):
self.cookie = cookie
self.user_id = user_id
self.time_min = time_min
self.time_max = time_max
self.videos = []
self.downloaded = 0
self.headers = {
'Accept': '*/*',
'Accept-Encoding': 'gzip, deflate, br',
'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:75.0) Gecko/20100101 Firefox/75.0',
'Content-Type': 'application/json',
'Origin': 'https://live.kuaishou.com',
'Host': 'live.kuaishou.com',
'Connection': 'keep-alive',
'Pragma': 'no-cache',
'Cache-Control': 'no-cache',
'Referer': 'https://live.kuaishou.com/profile/%s' % self.user_id,
'Cookie': self.cookie
}
self.noWaterMarkHeaders = {
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
"Accept-Language": "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2",
"Accept-Encoding": "gzip, deflate, br",
"Host": "kphbeijing.m.chenzhongtech.com",
'Connection': 'keep-alive',
'Pragma': 'no-cache',
'Cache-Control': 'no-cache',
"User-Agent": "Mozilla/5.0 (Android 9.0; Mobile; rv:68.0) Gecko/68.0 Firefox/68.0",
'Cookie': self.cookie
}
self.session = requests.Session()
self.session.headers.update(self.noWaterMarkHeaders)

def download(self, url, fileName=None, folder=None):
if (not fileName) or fileName.find('{default}') > -1:
end = url.find('?')
if end > -1:
default = url[url.rfind('/'):end]
else:
default = url[url.rfind('/'):]
if fileName:
fileName = fileName.replace('{default}', default)
else:
fileName = default

if not folder:
folder = 'download'

path = r'%s/%s' % (folder, fileName)
if not os.path.exists(path):
if not os.path.exists(folder):
os.makedirs(folder)

with open(path, "wb") as file:
response = requests.get(url, stream=True, timeout=120)
for data in response.iter_content(chunk_size=1024 * 1024):
file.write(data)
self.downloaded += len(data)
response.close()
time.sleep(2)
else:
time.sleep(5)

def getUrl(self, user_id, video_id):
# url = "https://live.kuaishou.com/m_graphql"
# param = '{"operationName":"SharePageQuery","variables":{"photoId":"%s",\
# "principalId":"%s"},"query":"query SharePageQuery($principalId: String, $photoId: String)\
# {\\n feedById(principalId: $principalId, photoId: $photoId) {\\n currentWork {\\n playUrl\\n\
# __typename\\n }\\n __typename\\n }\\n}\\n"}' % (video_id, user_id)
# data = requests.post(url, timeout=30, headers=self.headers, data=param)
# data = data.json()['data']
# '''
# 此处容易报错,应该是对请求的频率有限制
# '''
# print(video_id)
# url = data['feedById']['currentWork']['playUrl']
# return url
# 去水印版本 出错时请使用水印版本
url = "https://kphbeijing.m.chenzhongtech.com/fw/photo/%s" %video_id
# url = "https://www.kuaishou.com/short-video/%s" %video_id
res = self.session.get(url, timeout=30)
# video 地址
videourl = "https://www.kuaishou.com/short-video/%s" %video_id
videourlRes = self.session.get(videourl, timeout=30)
print(video_id)
searchObj = re.search(r'srcNoMark":"(http[^"]*)', res.text)

videourlSearchObj = re.search(r'srcNoMark":"(http[^"]*)', videourlRes.text)

if searchObj is None:
# 处理错误,例如打印错误信息或抛出异常
print("正则表达式匹配失败,没有找到匹配的字符串。")
return None # 或者你可以选择返回一个默认值或者抛出一个异常
url = searchObj.group(1)
return url

def getVideos(self):
url = "https://live.kuaishou.com/m_graphql"
param = "{\"operationName\":\"privateFeedsQuery\",\"variables\":{\"principalId\":\"%s\",\
\"pcursor\":\"\",\"count\":24},\"query\":\"query privateFeedsQuery($principalId: String, \
$pcursor: String, $count: Int) {\\n privateFeeds(principalId: $principalId, pcursor: $pcursor,\
count: $count) {\\n pcursor\\n list {\\n id\\n thumbnailUrl\\n poster\\n\
workType\\n type\\n useVideoPlayer\\n imgUrls\\n imgSizes\\n magicFace\\n\
musicName\\n caption\\n location\\n liked\\n onlyFollowerCanComment\\n\
relativeHeight\\n timestamp\\n width\\n height\\n counts {\\n displayView\\n\
displayLike\\n displayComment\\n __typename\\n }\\n\
user {\\n id\\n eid\\n name\\n avatar\\n __typename\\n }\\n\
expTag\\n __typename\\n }\\n __typename\\n }\\n}\\n\"}" % self.user_id
data = requests.post(url, timeout=10, headers=self.headers, data=param)
#print(data.text)
data = data.json()['data']['privateFeeds']
pcursor = data['pcursor']
is2Break = False
time_min = None
time_max = None
if self.time_min:
time_min = time.mktime(time.strptime(self.time_min, "%Y-%m-%d")) * 1000
if self.time_max:
time_max = time.mktime(time.strptime(self.time_max, "%Y-%m-%d")) * 1000
for obj in data['list']:
if not obj['id'] :
continue
video = {
'user_id': self.user_id,
'user_name': obj['user']['name'],
'video_id': obj['id'],
'workType': obj['workType'],
'caption': obj['caption'],
'timestamp': obj['timestamp'],
}
if(time_min and video['timestamp'] < time_min):
is2Break = True
break
if(time_max == None or video['timestamp'] < time_max + 1000*60*60*24):
if video['workType'] == 'video':
self.videos.append(video)

while (not is2Break) and pcursor and pcursor != 'no_more':
if pcursor == "":
print('cookie 失效')
return
param = "{\"operationName\":\"publicFeedsQuery\",\"variables\":{\"principalId\":\"%s\",\
\"pcursor\":\"%s\",\"count\":24},\"query\":\"query publicFeedsQuery($principalId: String,\
$pcursor: String, $count: Int) {\\n publicFeeds(principalId: $principalId, pcursor: $pcursor, count: $count)\
{\\n pcursor\\n live {\\n user {\\n id\\n avatar\\n name\\n __typename\\n\
}\\n watchingCount\\n poster\\n coverUrl\\n caption\\n id\\n playUrls {\\n\
quality\\n url\\n __typename\\n }\\n quality\\n gameInfo {\\n category\\n\
name\\n pubgSurvival\\n type\\n kingHero\\n __typename\\n }\\n hasRedPack\\n\
liveGuess\\n expTag\\n __typename\\n }\\n list {\\n id\\n thumbnailUrl\\n poster\\n\
workType\\n type\\n useVideoPlayer\\n imgUrls\\n imgSizes\\n magicFace\\n musicName\\n\
caption\\n location\\n liked\\n onlyFollowerCanComment\\n relativeHeight\\n timestamp\\n\
width\\n height\\n counts {\\n displayView\\n displayLike\\n displayComment\\n\
__typename\\n }\\n user {\\n id\\n eid\\n name\\n avatar\\n __typename\\n\
}\\n expTag\\n __typename\\n }\\n __typename\\n }\\n}\\n\"}" % (self.user_id, pcursor)
data = requests.post(url, timeout=10, headers=self.headers, data=param).json()['data']['publicFeeds']
pcursor = data['pcursor']
is2Break = False
for obj in data['list']:
video = {
'user_id': self.user_id,
'user_name': obj['user']['name'],
'video_id': obj['id'],
'workType': obj['workType'],
'caption': obj['caption'],
'timestamp': obj['timestamp'],
}
if(time_min and video['timestamp'] < time_min):
is2Break = True
break
if(time_max == None or video['timestamp'] < time_max + 1000*60*60*24):
if video['workType'] == 'video':
self.videos.append(video)

return self.videos

def refreshCookie(self):
'''
// TODO
'''
url = "https://id.kuaishou.com/pass/kuaishou/login/passToken"
headers = {
'Host': 'id.kuaishou.com',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:75.0) Gecko/20100101 Firefox/75.0',
'Accept': '*/*',
'Accept-Encoding': 'gzip, deflate, br',
'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',
'Content-Type': 'application/x-www-form-urlencoded',
'Origin': 'https://live.kuaishou.com',
'Referer': 'https://live.kuaishou.com/cate/my-follow/living',
'Connection': 'keep-alive',
'Pragma': 'no-cache',
'Cache-Control': 'no-cache',
'Cookie': self.cookie
}
params = 'sid=kuaishou.live.web'
data = requests.post(url, timeout=10, headers=headers, data=params).text
print(data)

2 changes: 1 addition & 1 deletion source/manager/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def __init__(self,
proxy: str | None,
work_path: "Path",
folder_name: str,
# cookie: str,
cookie: str,
cover: str,
music: bool,
download_record: bool,
Expand Down
13 changes: 10 additions & 3 deletions source/module/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ async def __connect_database(self):
await self.database.commit()

async def __create_table(self):
await self.database.execute("CREATE TABLE IF NOT EXISTS download_data (ID TEXT PRIMARY KEY);")
await self.database.execute("CREATE TABLE IF NOT EXISTS download_data (ID TEXT PRIMARY KEY, title TEXT);")
await self.database.execute(
"""CREATE TABLE IF NOT EXISTS config_data (
NAME TEXT PRIMARY KEY,
Expand Down Expand Up @@ -59,12 +59,19 @@ async def has_download_data(self, id_: str) -> bool:
await self.cursor.execute("SELECT ID FROM download_data WHERE ID=?", (id_,))
return bool(await self.cursor.fetchone())

async def write_download_data(self, id_: str):
async def write_download_data(self, id_: str, title_: str):
if self.record:
await self.database.execute(
"INSERT OR IGNORE INTO download_data (ID) VALUES (?);", (id_,))
"INSERT OR IGNORE INTO download_data (ID, title) VALUES (?, ?);",(id_, title_))
await self.database.commit()

async def update_download_data(self, id_: str, title_: str):
if self.record:
await self.database.execute(
"UPDATE download_data SET ID = ?, title = ? WHERE ID = ?;",
(id_, title_, id_)
)
await self.database.commit()
async def delete_download_data(self, ids: list | tuple | str):
if not self.record:
return
Expand Down
2 changes: 1 addition & 1 deletion source/tools/string.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ def is_chinese_char(char):
return False


def truncation(string: str, length=64) -> str:
def truncation(string: str, length=256) -> str:
result = ""
for s in string:
result += s
Expand Down
Loading