async def test1(): client = PixivClient(env=True).start() api = AppPixivAPI(client=client) api.login(refresh_token=_TOKEN) # await api.login(_USERNAME, _PASSWORD) await client.close() print("test1 - finished")
def __init__(self, account: int, a_token: str, r_token: str, recent_update: list, save_path: str, logger_, proxy: Optional[str]): """ Pixiv对象,储存个人账号信息进行更新、下载(不支持gif),支持代理(SOCKS5),支持自定义logger :param account: qq账号id :param a_token: Pixiv账号access_token :param r_token: Pixiv账号refresh_token :param recent_update: 最近更新列表 :param save_path: 图片存储路径 :param logger_: 日志对象 :param proxy: 代理链接 """ if proxy: self.api = Api(proxy=proxy) else: self.api = Api() self.skip_page = "ugoira" # self.save_path = "./function/pixiv_img" self.save_path = save_path self.account = account self.a_token = a_token self.r_token = r_token self.recent_update = recent_update self.proxy = proxy self.login_command() self.logger = logger_
async def init_appapi(aapi: AppPixivAPI): try: await aapi.login(username=self.Config.pixiv_username, password=self.Config.pixiv_password) except: logger.exception("Pixiv 登陆失败") return False logger.info("成功登录 Pixiv") aapi.set_accept_language("zh-CN") return True
async def _test_async_illust_detail(num): async with PixivClient() as client: aapi = AppPixivAPI(client=client) papi = PixivAPI(client=client) await aapi.login(_USERNAME, _PASSWORD) await papi.login(_USERNAME, _PASSWORD) tasks = [asyncio.ensure_future(illust_detail(aapi, i)) for i in range(num)] await asyncio.wait(tasks)
async def get_by_ranking(mode="day", num=3): async with PixivClient(proxy=proxy, timeout=20) as client: aapi = AppPixivAPI(client=client, proxy=proxy) await aapi.login(refresh_token=pixiv_config.pixiv_token) illusts = await aapi.illust_ranking(mode) illusts = illusts["illusts"] random.shuffle(illusts) return illusts[0:num]
async def _test_async_me_following_works(num): async with PixivClient() as client: aapi = AppPixivAPI(client=client) papi = PixivAPI(client=client) await aapi.login(_USERNAME, _PASSWORD) await papi.login(_USERNAME, _PASSWORD) tasks = [asyncio.ensure_future(me_following_works(papi, i)) for i in range(num)] await asyncio.wait(tasks)
def __init__(self, client: NanoClient, pixiv_client: PixivClient()): self.name = "Pixiv" self.client = client self.aapi = AppPixivAPI(client=pixiv_client.start()) self.day_pool = {} self.day_male_pool = {} self.day_female_pool = {} self.base_url = 'https://www.pixiv.net/en/artworks/' asyncio.ensure_future(self.load_pools_async())
def __init__(self, **kwargs): super().__init__(**kwargs) # forces reauth() to trigger if any method is called: self.last_auth = datetime.datetime.fromtimestamp(0) self.username = "" self.password = "" self.aapi = AppPixivAPI(**kwargs) self.papi = PixivAPI(**kwargs)
async def _test_async_ranking(num): async with PixivClient() as client: aapi = AppPixivAPI(client=client) papi = PixivAPI(client=client) await papi.login(refresh_token=_TOKEN) await aapi.login(refresh_token=_TOKEN) # await aapi.login(_USERNAME, _PASSWORD) # await papi.login(_USERNAME, _PASSWORD) tasks = [asyncio.ensure_future(ranking(papi, i)) for i in range(num)] await asyncio.wait(tasks)
class PixivParser: status = False aapi = AppPixivAPI() id_regex = r"[1-9]\d*" class Config(BaseConfig, config_file="push_config.json"): pixiv_refresh_token: str = str() @classmethod def _check(cls, _attr_name: str, _attr_value: Any) -> Tuple[str, Any]: if _attr_name == "pixiv_refresh_token" and _attr_value == "": logger.exception(f"非法的 refresh_token。如果您不知道这是什么,请查看文档") else: return _attr_name, _attr_value @classmethod def send(self, url: str, classification: str, bot: Bot, target): def origin_link_button(_id: int) -> InlineKeyboardMarkup: return InlineKeyboardMarkup([[ InlineKeyboardButton( text="原链接", url=f"https://www.pixiv.net/artworks/{_id}") ]]) illust_id = int(re.search(pattern=self.id_regex, string=url).group(0)) try: illust = Illust(illust_id, self.Config.pixiv_refresh_token) except IllustInitError: return illust.download() images = illust.get_downloaded_images() if len(images) > 1: bot.send_media_group( chat_id=target, media=[InputMediaPhoto(BytesIO(image)) for image in images]) bot.send_message(text=str(illust), chat_id=target, reply_markup=origin_link_button(illust_id), disable_web_page_preview=True, parse_mode=ParseMode.HTML) else: print(str(illust)) bot.send_photo(photo=BytesIO(images[0]), chat_id=target, caption=str(illust), reply_markup=origin_link_button(illust_id), parse_mode=ParseMode.HTML) logger.info(f"Pixiv: 成功推送 {illust.id}")
async def get_by_search(keyword, num=3): async with PixivClient(proxy=proxy, timeout=20) as client: aapi = AppPixivAPI(client=client, proxy=proxy) await aapi.login(refresh_token=pixiv_config.pixiv_token) illusts = await aapi.search_illust(keyword) illusts = illusts["illusts"] illusts = sorted(illusts, key=lambda i: i["total_bookmarks"], reverse=True) if len(illusts) > num * 3: illusts = illusts[0:int(len(illusts) / 2)] random.shuffle(illusts) return illusts[0:min(num, len(illusts))]
async def to_msg(illusts) -> Message: msg = Message() async with PixivClient(proxy=proxy, timeout=20) as client: aapi = AppPixivAPI(client=client, proxy=proxy) await aapi.login(refresh_token=pixiv_config.pixiv_token) for illust in illusts: try: url: str = illust["image_urls"]["large"] url = url.replace("_webp", "").replace("i.pximg.net", "i.pixiv.re") async with httpx.AsyncClient() as client: resp = await client.get(url, timeout=20) result = resp.content if result: msg.append("{} ({})".format(illust["title"], illust["id"])) msg.append(MessageSegment.image(result)) except Exception as e: logger.warning(f"Error downloading image: {e}") return msg
async def test_pixivpy_async_app_api(): client = PixivClient(env=True) aapi = AppPixivAPI(client=client.start()) # conn = aiohttp.TCPConnector(limit_per_host=30) # session = aiohttp.ClientSession( # connector=conn, # timeout=aiohttp.ClientTimeout(total=10), # trust_env=True, # ) # client.client = session username, password = os.environ['PIXIV_USERNAME'], os.environ['PIXIV_PASS'] # For App Pixiv API await aapi.login(username, password) res = await aapi.illust_ranking("day") illusts = res.get("illusts") print(len(illusts), illusts[0]) await client.close()
async def get_by_id(work_id): async with PixivClient(proxy=proxy, timeout=20) as client: aapi = AppPixivAPI(client=client, proxy=proxy) await aapi.login(refresh_token=pixiv_config.pixiv_token) illust = await aapi.illust_detail(work_id) return illust
class PixivParser: status = False aapi = AppPixivAPI() id_regex = r"[1-9]\d*" class Config(BaseConfig, config_file="push_config.json"): pixiv_username: str = str() pixiv_password: str = str() download_path: Optional[str] = str() @classmethod def _check(cls, _attr_name: str, _attr_value: Any) -> Tuple[str, Any]: if _attr_name == "download_path" and _attr_value == "": logger.info(f"未提供下载路径, 使用 {os.path.dirname(__file__)}/PixivDownload") try: os.mkdir(f"{os.path.dirname(__file__)}/PixivDownload") except FileExistsError: pass except BaseException as exc: logger.exception("下载路径不可用") #sys.exit(1) raise exc return _attr_name, os.path.dirname(__file__) + "/PixivDownload" else: return _attr_name, _attr_value @classmethod @run_async def send(self, url: str, classification: str, bot: Bot, target): async def init_appapi(aapi: AppPixivAPI): try: await aapi.login(username=self.Config.pixiv_username, password=self.Config.pixiv_password) except: logger.exception("Pixiv 登陆失败") return False logger.info("成功登录 Pixiv") aapi.set_accept_language("zh-CN") return True def origin_link(_id: int): return InlineKeyboardMarkup([[InlineKeyboardButton(text="原链接", url=f"https://www.pixiv.net/artworks/{_id}")]]) def parse_tags_text(tags: list) -> str: text = str() for tag in tags: if tag.translated_name: translated_name = f"({tag.translated_name})" else: translated_name = "" text = text + \ f"<a href=\"https://www.pixiv.net/tags/{tag.name}/artworks\">{tag.name}{translated_name}</a> " return text async def download_single_pic(url: str, _id: int, size: str, page: int, aapi: AppPixivAPI): url_basename = os.path.basename(url) extension = os.path.splitext(url_basename)[1] name = f"{_id}_p{page}_{size}{extension}" try: os.mkdir(f"{self.Config.download_path}/{_id}") except FileExistsError: pass await aapi.download(url, path=self.Config.download_path + "/" + str(_id), name=name) logger.info(f"成功下载 {name}") def download_pic(urls: list, _id: int, size: str, aapi: AppPixivAPI): page = 0 loop = asyncio.new_event_loop() tasks = list() for url in urls: tasks.append(download_single_pic(url, _id, size, page, aapi)) # download_single_pic(url, _id, size, page, aapi) page = page + 1 loop.run_until_complete(asyncio.gather(*tasks, loop=loop)) loop.close() logger.info(f"成功下载 {_id} 全部图片") async def parse_illust_info_msg(illust_id: int, aapi: AppPixivAPI): json_result = await aapi.illust_detail(illust_id) info = json_result.illust caption = str() if info.caption != "": soup = BeautifulSoup(info.caption, "html.parser") caption = "\n" + soup.get_text() msg_text = f"<b>标题:</b>{info.title}\n<b>作者:</b><a href=\"https://www.pixiv.net/users/{info.user.id}\">{info.user.name}</a>{caption}\n<b>标签:</b>{parse_tags_text(info.tags)}{classification}" logger.info(msg_text) if info.page_count == 1: illust_urls = [info.image_urls.large] else: illust_urls = [page.image_urls.large for page in info.meta_pages] return illust_urls, illust_id, msg_text loop = asyncio.new_event_loop() login_result = loop.run_until_complete(init_appapi(self.aapi)) loop.close() if not login_result: return illust_id = int(re.search(pattern=self.id_regex, string=url).group(0)) loop = asyncio.new_event_loop() illust_urls, illust_id, msg_text = loop.run_until_complete( parse_illust_info_msg(illust_id, self.aapi)) loop.close() download_pic(illust_urls, illust_id, "large", self.aapi) file_dirs = [self.Config.download_path+f"/{illust_id}/" + filename for filename in os.listdir(self.Config.download_path+f"/{illust_id}")] if len(file_dirs) == 1: bot.send_photo(chat_id=target, photo=open(file_dirs[0], 'rb'), caption=msg_text, reply_markup=origin_link(illust_id), parse_mode=ParseMode.HTML) else: tmp_sub_file_group = list() tmp_size = 0 sub_file_groups = list() for file_dir in file_dirs: if tmp_size + os.path.getsize(file_dir) <= 5242880 and len(tmp_sub_file_group) + 1 <= 10: tmp_sub_file_group.append(InputMediaPhoto(media=open(file_dir, 'rb'), caption=msg_text, parse_mode=ParseMode.HTML)) else: sub_file_groups.append(tmp_sub_file_group) tmp_sub_file_group = [InputMediaPhoto(media=open(file_dir, 'rb'), caption=msg_text, parse_mode=ParseMode.HTML)] tmp_size = os.path.getsize(file_dir) sub_file_groups.append(tmp_sub_file_group) for sub_file_group in sub_file_groups: bot.send_media_group(chat_id=target, media=sub_file_group) bot.send_message(chat_id=target, text=msg_text, reply_markup=origin_link(illust_id), disable_web_page_preview=True, parse_mode=ParseMode.HTML)
def main(): loop = asyncio.get_event_loop() loop.run_until_complete(_main(AppPixivAPI()))
class Illust: aapi = AppPixivAPI(env=True) def __init__(self, illust_id: int, refresh_token:str): async def init_appapi() -> bool: try: await self.aapi.login(refresh_token=refresh_token) except: return False self.aapi.set_accept_language("zh-CN") return True async def get_info() -> bool: try: json_result = await self.aapi.illust_detail(self.id) except: return False info = json_result.illust self.caption = str() if info.caption != "": soup = BeautifulSoup(info.caption, "html.parser") self.caption = "\n" + soup.get_text() self.title = info.title self.author = (info.user.name, info.user.id) self.tags = [(tag.name, tag.translated_name) for tag in info.tags] if info.page_count == 1: self.urls = [info.image_urls.large] self.original_urls = [info.meta_single_page.original_image_url] else: self.urls = [ page.image_urls.large for page in info.meta_pages] self.original_urls = [ page.image_urls.original for page in info.meta_pages] return True loop = asyncio.new_event_loop() login_result = loop.run_until_complete(init_appapi()) loop.close() if not login_result: logger.exception("Pixiv 登录失败") raise LoginError logger.info("Pixiv 登录成功") self.id: int = illust_id self.__images: list = list() loop = asyncio.new_event_loop() get_info_result = loop.run_until_complete(get_info()) loop.close() if not get_info_result: logger.exception("插画信息获取失败") raise GetInfoError def __str__(self): tags_text = str() for tag in self.tags: tags_text = tags_text + \ f"<a href=\"https://www.pixiv.net/tags/{tag[0]}/artworks\">{tag[0]}</a>" if tag[1]: tags_text = tags_text + f" ({tag[1]})" tags_text = tags_text+", " return f"<b>标题:</b>{self.title}\n<b>作者:</b><a href=\"https://www.pixiv.net/users/{self.author[1]}\">{self.author[0]}</a>\n<b>简介:</b>{self.caption}\n<b>标签:</b>{tags_text}" async def __download_single_image(self, url: str, size_hint: str, page_hint: int): try: content, type = await self.aapi.down(url, "https://app-api.pixiv.net/") except: logger.exception(f"{self.id} {size_hint} 第 {page_hint} 张下载错误") raise DownloadError if type is not None and type.find("image") != -1: self.__images.append((page_hint, content)) else: logger.exception(f"{self.id} {size_hint} 第 {page_hint} 张下载错误") raise DownloadError logger.info(f"成功下载 {self.id} {size_hint} 第 {page_hint} 张") def __download_images(self, original: bool = False): page = 0 loop = asyncio.new_event_loop() if not original: urls = self.urls size_hint = "large" else: urls = self.original_urls size_hint = "original" tasks = list() for url in urls: tasks.append(self.__download_single_image(url, size_hint, page)) page = page + 1 try: loop.run_until_complete(asyncio.gather(*tasks, loop=loop)) except DownloadError: pass finally: loop.close() logger.info(f"成功下载 {self.id} 全部 {size_hint} 图片") def download(self): self.__download_images() def download_original(self): self.__download_images(True) def get_downloaded_images(self): if not len(self.__images): return None self.__images.sort(key=lambda elem: elem[0]) return [elem[1] for elem in self.__images[:9]]
class Pixiv: def __init__(self, account: int, a_token: str, r_token: str, recent_update: list, save_path: str, logger_, proxy: Optional[str]): """ Pixiv对象,储存个人账号信息进行更新、下载(不支持gif),支持代理(SOCKS5),支持自定义logger :param account: qq账号id :param a_token: Pixiv账号access_token :param r_token: Pixiv账号refresh_token :param recent_update: 最近更新列表 :param save_path: 图片存储路径 :param logger_: 日志对象 :param proxy: 代理链接 """ if proxy: self.api = Api(proxy=proxy) else: self.api = Api() self.skip_page = "ugoira" # self.save_path = "./function/pixiv_img" self.save_path = save_path self.account = account self.a_token = a_token self.r_token = r_token self.recent_update = recent_update self.proxy = proxy self.login_command() self.logger = logger_ def login_command(self): """ 使用内置的token设置登录pix :return: """ self.api.set_auth(self.a_token, self.r_token) async def refresh_token(self): """ 依据内置的refresh_token进行token刷新,同时进行token登录设置 :return: """ a, r = await Pix.reset_token(self.account, self.r_token) self.a_token = a self.r_token = r self.logger.info( "{}的重置Token:'access_token - {}' 'refresh_token - {}'".format( str(self.account), a, r)) self.login_command() self.logger.info("{}的Token 写入文件成功".format(str(self.account))) async def _get_new_follow_art(self, retry: bool = False): """ 获取关注列表的新图,列表结果已进行比对筛选去除已更新 :param retry: 当token失效自动刷新登录,无需手动设置 :return: 图片列表[[pid,page]...] """ try: self.logger.info("向api请求中") json_all = await self.api.illust_follow() except BaseException as e: self.logger.error(e) else: if "error" in json_all and retry is False: self.logger.info("{}的Token失效".format(str(self.account))) await self.refresh_token() return await self._get_new_follow_art(retry=True) self.logger.info("Token有效,获得数据中") real_pid_list = [] json_res = json_all["illusts"] last_save_pid_list = self.recent_update art_list = [] nums = 0 for img in json_res: pid = str(img['id']) if img['type'] == self.skip_page or pid in last_save_pid_list: pass else: meta_single_page = img['meta_single_page'] meta_pages = img['meta_pages'] if meta_single_page: art_list.append( [pid, meta_single_page['original_image_url']]) elif meta_pages: pages = [] for page in meta_pages: pages.append(page['image_urls']["original"]) art_list.append([pid, pages]) nums += 1 real_pid_list.append(pid) if nums >= 20: break self.logger.info("json数据获取结束") return art_list, real_pid_list @classmethod def _resolve_art_list(cls, art_list: list): """ 根据结果对url列表进行分离整合 :param art_list: Pix_url_list :return: pid列表, 单个pid包含图片数列表, 纯url列表 """ pid_list = [] pages_count_list = [] url_list = [] for art in art_list: pid_list.append(art[0]) pages_info = art[1] if isinstance(pages_info, str): pages_info = [pages_info] pages_count_list.append(len(pages_info)) url_list.extend(pages_info) return pid_list, pages_count_list, url_list async def _url_to_path(self, urls: List[str]) -> \ Tuple[List[str], List[List[Union[int, str]]], List[List[Union[int, BytesIO]]]]: """ 根据url列表,转化为储存路径列表,并对url进行反向代理链接替换 :param urls: url_list :return: paths: Tuple[List[str], url_list: List[List[Union[int, str]]], downloaded_img: List[List[Optional[int, BytesIO]]]] """ if isinstance(urls, str): urls = [urls] paths = [] num_to_urls = list(zip([i for i in range(len(urls))], urls)) downloaded_img = [] url_list = [] for url in num_to_urls: path = os.path.join(self.save_path, url[1].split("/")[-1]) if os.path.exists(path): async with aiofile.async_open(path, "rb") as f: downloaded_img.append([url[0], BytesIO(await f.read())]) else: url_list.append( [url[0], url[1].replace("i.pximg.net", "i.pixiv.cat")]) # url_list.append([url[0], url[1]]) paths.append(path) return paths, url_list, downloaded_img @classmethod async def _downloader(cls, download_url: List[List[Union[int, str]]], logger, proxy: Optional[str] = None) \ -> List[List[Union[int, BytesIO]]]: """ 下载所有图片链接, 返回二进制数据列表 :param download_url: url_list :return: bytes_data_list """ down = [] index_date = [i[0] for i in download_url] n = 0 if proxy: proxy_info_list = proxy.split(":") port = int(proxy_info_list[-1]) ip = proxy_info_list[1].split("/")[-1] # hander = { # "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4471.0 Safari/537.36 Edg/91.0.864.1", # "Referer": "https://www.pixiv.net/" # } connector = ProxyConnector(proxy_type=ProxyType.SOCKS5, host=ip, port=port) async with aiohttp.ClientSession(connector=connector) as session: for url in download_url: logger.info(f"请求url:{url}") img = await cls._aiohttp_down(session, url) down.append([index_date[n], img]) n += 1 else: async with aiohttp.ClientSession() as session: for url in download_url: img = await cls._aiohttp_down(session, url) down.append([index_date[n], img]) n += 1 return down @classmethod async def _aiohttp_down(cls, session: aiohttp.ClientSession, url: list[Union[int, str]], re: int = 0): async with session.get(url[1]) as response: if response.status == 200: img = BytesIO(await response.read()) return img else: if re > 3: raise ConnectionError else: return await cls._aiohttp_down(session, url, re + 1) @classmethod def _zip_img(cls, downloaded: List[List[Union[int, BytesIO]]], now_download: List[List[Union[int, BytesIO]]]) -> \ List[BytesIO]: """ 合并 :param downloaded: :param now_download: :return: """ size_img = len(downloaded) + len(now_download) res_list = [BytesIO()] * size_img for i in downloaded: res_list[i[0]] = i[1] for i in now_download: res_list[i[0]] = i[1] return res_list async def _save_from_downloader(self, pages_path: list, img_bytes: List[List[Union[int, BytesIO]]]): """ 下载至对应路径 :param pages_path: list :param img_bytes: List[BytesIO] :return: """ img_len = len(pages_path) for i in range(img_len): async with aiofile.async_open(pages_path[i], "wb") as f: await f.write(img_bytes[i][1].getvalue()) self.logger.info("{}张图片保存成功".format(str(img_len))) def _creat_msg_chain(self, pid_list: List[str], pages_num: List[int], img_bytes: List[BytesIO]): """ 进行消息链构造 :param pid_list: List[str] :param pages_num: List[int] :param img_bytes: List[BytesIO] :return: List[List[MessageChain], ...] """ page_count = 0 msg_img_limit = 2 limit_count = 0 out_msg_list = [[ Plain("又到了涩图时间!此次共有{}张图!".format(str(len(img_bytes)))) ]] for i in range(len(pid_list)): out_msg = [] for n in range(pages_num[i]): out_msg.append( Image.fromUnsafeBytes(img_bytes[page_count].getvalue())) page_count += 1 limit_count += 1 if limit_count >= msg_img_limit: limit_count = 0 out_msg_list.extend([out_msg]) out_msg = [] if out_msg: out_msg_list.extend([out_msg]) limit_count = 0 self.logger.info("消息链构建成功") return out_msg_list async def _change_recent_save(self, pid: list): """ 保存更新作品pid :param pid: list :return: """ await Pix.change_recent_save(self.account, pid) self.logger.info("{}的最近更新已正常写入".format(str(self.account))) async def run(self): """ pix更新 :return: List[List[MessageChain], ...] """ self.logger.info("即将获取更新列表") res, last_pid_list = await self._get_new_follow_art() if not res: self.logger.info("操作完成,无作品更新") pass else: self.logger.info("json分离结束") pid_list, pages_num, url_list = self._resolve_art_list(res) self.logger.info("进行链接代理转化") pages_path, new_urls, downloaded_bt_list = await self._url_to_path( url_list) self.logger.info("进行下载") img_bytes = await self._downloader(new_urls, self.logger, self.proxy) self.logger.info("下载完成,进行打包") fin_img_list = self._zip_img(downloaded_bt_list, img_bytes) self.logger.info("打包完成,保存至本地文件") await self._save_from_downloader(pages_path, img_bytes) self.logger.info("保存完毕,开始构建消息链") out_msg_list = self._creat_msg_chain(pid_list, pages_num, fin_img_list) await self._change_recent_save(last_pid_list) self.recent_update = last_pid_list return out_msg_list