yinghihi 发表于 2021-7-1 00:06

漫画db全站爬虫,写了同步异步两个版本,不得不感叹下协程的效率

之前的python爬虫练习之作,今天测试了下,还可以用,话不多说,先直接上同步版本。
comic.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2021/4/30 21:33
# @AuThor : yinghihi
import requests
import re
import os
from lxml import etree
from requests.exceptions import RequestException
from threading import Thread

requests.DEFAULT_RETRIES = 5
requests = requests.session()
requests.keep_alive = False

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.85 Safari/537.36 Edg/90.0.818.46'}


def get_url(url):
    """网页请求"""
    try:
      response = requests.get(url, headers=headers)
      if response.status_code == 200:
            html_res = response.text
            # 搜索结果超过48后进行分页匹配
            search_page_num_re = re.compile('<option value="/search\?q=.*?(p=\d+)".*?>第.*?页</option>', re.S)
            search_page_num = re.findall(search_page_num_re, html_res)
            s_url_res = []
            for n in search_page_num:
                s_url = url + '&' + n
                s_url_s = requests.get(s_url, headers=headers).text
                s_url_res.append(s_url_s)
            return s_url_res

    except RequestException as e:
      raise


def select_comic(s_url_res):
    # 搜索结果判断,无结果则提示重新输入
    if not s_url_res:
      print("抱歉,没有找到符合条件的漫画(*'ω' *)")
      print('*' * 60)
      url2 = 'https://www.manhuadb.com/search?q=' + input('请重新输入漫画名/作者:')
      main(url2)
    # 初始化一个以1开始的索引数字序列和索引字典index_dict
    num = 1
    index_dict = {}
    # 有搜索结果,则遍历所有搜索结果的标题和网址
    print('搜索到以下结果:')
    print('*' * 60)
    for html_res in s_url_res:
      parse_html = etree.HTML(html_res)
      search_title = parse_html.xpath('//div/a/@title')
      search_title_url = parse_html.xpath('//div/a/@href')
      # 定义一个空列表来存放所有搜索到的漫画完整url地址
      search_title_url_full = []
      for i in search_title_url:
            # 拼接出完整的漫画浏览页网址
            i = 'https://www.manhuadb.com' + i
            search_title_url_full.append(i)
      # 将所有搜索到的漫画标题和url合并为一个有对应关系的字典
      search_dict = dict(zip(search_title, search_title_url_full))
      for k, v in search_dict.items():
            # 将数字序列和字典键值拼接展示给用户(用户选择)
            print(str(num) + ',' + k + ':' + v)
            # 创建一个键从1开始递增,值为url的索引字典(用于爬虫选择要爬取的url)
            index_dict = v
            num += 1
    print('*' * 60)
    # 用户输入要下载漫画序号,和索引字典的键对比,相等则爬取对应的漫画url
    select_num = int(input('请输入你要下载的漫画序号:'))
    for key, value in index_dict.items():
      if key == select_num:
            select_url = index_dict
            select_url_html = requests.get(select_url, headers=headers).text
            # 漫画版本下载控制
            version_parse = etree.HTML(select_url_html)
            version = version_parse.xpath('//ul/li[@class="nav-item"]/a/span/text()')
            version_id = version_parse.xpath('//ul/li[@class="nav-item"]/a/@href')
            version_f_id = []
            for v_id in version_id:
                version_f_id.append(v_id)
            version_dict = dict(zip(version, version_f_id))
            v_name = version_parse.xpath('//div/h1/text()')
            print(f'你选择的漫画【{v_name}】有以下版本可以选择:')
            for version_title in version:
                print(f'{version_dict}=>{version_title}')
            print('*' * 60)
            select_ver_num = (input('请输入版本序号:'))
            version_url = version_parse.xpath(f'//div[@id="{select_ver_num}"]//li/a/@href')
            final_url = 'https://www.manhuadb.com' + version_url
            final_content = requests.get(final_url, headers=headers).text
            return final_content
      else:
            continue


def parse_html(res):
    all_cha_recompile = re.compile(
      '<li class="sort_div.*?" data-sort="\d+">\n.*?<a class=".*?" href="\/manhua\/(.*?)">.*?<\/a>\n.*?<\/li>',
      re.S)
    all_cha_url = re.findall(all_cha_recompile, res)
    for i in all_cha_url:
      all_full_url = 'https://www.manhuadb.com/manhua/' + i
      all_full_content = requests.get(all_full_url, headers=headers).text
      page_num_recompile = re.compile(
            '<li class="breadcrumb-item active" aria-current="page">\n.*?<a href="\/manhua\/.*?">.*?<\/a> \/ 第 <span class="c_nav_page">\d+<\/span> 页・共 (\d+) 页\n.*?<\/li>',
            re.S)
      page_nums = re.findall(page_num_recompile, all_full_content)
      for num in range(1, int(page_nums) + 1):
            full_urls = all_full_url[:-5] + '_p' + str(num) + all_full_url[-5:]
            cox = requests.get(full_urls).text
            yield cox


def img_parse(img_content):
    for item in img_content:
      title_recompile = re.compile(
            '<li class="breadcrumb-item active" aria-current="page">\n.*?<a href="\/manhua\/.*?">(.*?)<\/a> \/ 第 <span class="c_nav_page">\d+<\/span> 页・共 \d+ 页\n.*?<\/li>',
            re.S)
      recompile = re.compile(
            '<img class="img-fluid show-pic" src="(.*?)" />', re.S)
      title = re.findall(title_recompile, item)
      src = re.findall(recompile, item)
      pic_request = requests.get(src, headers=headers)
      img_name_re = re.compile(
            '<img class="img-fluid show-pic" src="https:\/\/i\d+.manhuadb.com\/.*?\/.*?\/.*?\/(.*?)" />', re.S)
      img_name = re.findall(img_name_re, item)
      comic_name_re = re.compile('<h1 class="h2 text-center mt-3 ccdiv-m"><a href=".*?">(.*?)<\/a><\/h1>', re.S)
      comic_name = re.findall(comic_name_re, item)
      yield {'comic_name': comic_name, 'img_name': img_name, 'img_content': pic_request.content,
               'chapter_title': title}


def write_res(item):
    comic_path = item['comic_name']
    dict_res = item['chapter_title']
    if not os.path.exists(comic_path + '/' + dict_res):
      os.makedirs(comic_path + '/' + dict_res)
      with open(comic_path + '/' + dict_res + '/' + item['img_name'], 'wb') as f:
            print('正在下载:' + dict_res + '/' + item['img_name'])
            f.write(item['img_content'])
    else:
      if os.path.exists(comic_path + '/' + dict_res + '/' + item['img_name']):
            print(f'{item["img_name"]}已存在,忽略下载!!!')
      else:
            with open(comic_path + '/' + dict_res + '/' + item['img_name'], 'wb') as f:
                print('正在下载:' + dict_res + '/' + item['img_name'])
                f.write(item['img_content'])


def thredings(img_res):
    for item in img_res:
      t = Thread(target=write_res, args=(item,))
      t.start()


def main(offset):
    html = get_url(offset)
    selecter = select_comic(html)
    print('资源序号已选择,开始解析!')
    response = parse_html(selecter)
    print('页面解析完毕,获取所有漫画页面资源中···!')
    img_res = img_parse(response)
    print('开始下载···')
    print('*' * 60)
    # write_res(img_res)
    thredings(img_res)


if __name__ == '__main__':
    print('=====程序开始执行=====')
    url = 'https://www.manhuadb.com/search?q=' + input('请输入漫画名/作者:')
    main(url)

截图
https://i.loli.net/2021/06/30/tge2VTCYGjK6w9D.png
https://i.loli.net/2021/06/30/QLnGJkpaBM9oEiN.png
https://i.loli.net/2021/06/30/WmQOj2aD367bHcu.png

https://static.52pojie.cn/static/image/hrline/1.gif



重点来了,先吹一波异步协程的效率,对比用requests同步请求下载的爬虫,快了10倍不止。如果大家想真的写出个实用的高效爬虫,异步绝对不能不了解。
我也是经过不断试错,今天才算勉强了解asyncio和aiohttp的基本用法,网上能用于实战的教程实在太少了,大部分都是雷同的,想请教下坛子里的大佬们,不知道哪里能找到比较完善易懂的异步教程。


https://static.52pojie.cn/static/image/hrline/1.gif



下面关于我写的这个爬虫异步版本:
因为是验证异步下载效果的,所以很多变量和代码就写的很随便了,很难读。另外就是异步爬虫的异常捕获我还不会,细节有待完善,等我完善好再发出来,今天主要先安利一波,发截图给大家看看异步的效率。
这里以下载漫画【死神】为例:(74卷,2.99GB,13973张,因为网站在国外,晚上速度不理想,用requests请求基本1-2秒一张,数量太多没测试完)
https://ftp.bmp.ovh/imgs/2021/06/19462dbc113b2e37.png
https://ftp.bmp.ovh/imgs/2021/06/e325b7528af5007c.png
https://ftp.bmp.ovh/imgs/2021/06/836a9981f98213a6.png
https://ftp.bmp.ovh/imgs/2021/06/2fd06b1a3221b869.png
可以看到,爬完整部漫画,用异步只需要1154秒(如果是白天网速理想的情况下,应该可以更快的),而如果用同步至少要14000秒以上了,明显异步要更实用些。

roshinntou 发表于 2021-7-2 11:27

本帖最后由 roshinntou 于 2021-7-2 11:31 编辑

我们缺的是异步协程吗~?{:301_999:}

不是,我们缺的是这种耿直的拼写漫画每页url、直接把图片写在html代码里不动态加载、没有限制IP访问的漫画网站呀{:301_972:}




总之谢谢分享了。好像现在img_parse方法获取不到具体的图片地址了,选好漫画之后返回的结果是空,最后直接瞬间结束了。

zucker 发表于 2021-7-2 22:29

yinghihi 发表于 2021-7-2 20:58
这个限制下并发数大部分都能解决,再不行上代{过}{滤}理池

限制并发就减速了。袋里一样的减速

慕容语嫣 发表于 2021-7-1 02:40

感谢分享呀

keyyan 发表于 2021-7-1 07:19

大佬讲讲asyncio和aiohttp的基本用法呗,外行人看百度来的都看不太明白

hshcompass 发表于 2021-7-1 08:39


感谢分享呀

CCQc 发表于 2021-7-1 08:40

看大佬们的爬爬分析,感谢分享!

科西嘉滕 发表于 2021-7-1 08:41

谢谢分享

yuleniwo 发表于 2021-7-1 08:44

谢谢分享代码。收藏了

zhangfeng_223 发表于 2021-7-1 09:26

谢谢分享

kiviki 发表于 2021-7-1 09:32

学习借鉴下,异步的方法正好也在研究中

love514415 发表于 2021-7-1 09:37

膜拜大佬,感谢分享
页: [1] 2 3
查看完整版本: 漫画db全站爬虫,写了同步异步两个版本,不得不感叹下协程的效率