python多线程爬取某网站全部H漫画

python多线程爬取某网站全部H漫画

首发于个人博客(客官大人来这里看啊!):www.gunnerx.vip

前言

最近学习python多线程与爬虫相关知识,想试着练练手。正好想到常逛的一个正(瑟)经(琴)漫画的网站,决定想办法把上面全部漫画都爬下来,以便✋

声明

事先声明,我只是个刚学爬虫不久的菜鸡,所以代码应该有很多有问题,和可以改进的地方,希望大家轻喷但是多多评论帮我指出问题

分析

要写爬虫首先当然是要分析网页喽。谷歌chrome和火狐firefox都能打开开发者工具分析,不过因为火狐的是中文界面,所以就选择fx。

打开网站,https://www.xxxxx.com (网址打码!)

由于过于瑟琴,这里就不放图了。

分析一番,发现比如一本h漫画叫a,那它的url就是https://www.xxxxx.com/中文h漫/a/1,它每一页的url,比如第3页,就是https://www.xxxxx.com/中文h漫/a/1/page/3。对每一页所在的页面分析,发现漫画的每一页的图片的真实url就在页面html的一个img标签中,如图

python多线程爬取某网站全部H漫画_第1张图片

如图的img标签中的src属性便是图片的真实url,只要请求这个url便可卸载此张图片。

因为每一本漫画的所有页url都是按规律的,所以只要得到每本漫画的总页数便可构造他全部页的url,进而得到每一个图片的真实url,进行下载。

分析发现,漫画的每一个页面中都有一个快速选择页数的小控件,如图

在这里插入图片描述

分析html页面找到其对应的标签:

python多线程爬取某网站全部H漫画_第2张图片

因此,只要找到这个标签便可解析得到总页数。

以上是针对如何爬取一本漫画的所有图片,若要爬取所有页面,只需先写一个爬虫把所有网站上所有h漫画的名字爬下保存下来,即可构建url。

综上,大致思路如下:

1.首先请求页面 https://www.xxxxx.com想办法爬取所有的漫画名字列表names(一共32页382本漫画)

2.对每一个漫画名字name的某一页p可构建出url:https://www.xxxxx.com/中字h漫/name/1/page/p

3.请求此页面并解析此页面,找到此页漫画图片的真实url地址jpg_url

3.请求jpg_url,以二进制形式下载图片并保存至本地

技术选型

技术选型方面,考虑使用requests库来构建http请求;引入concurrent.future库,维护一个线程池来实现多线程爬虫;html页面解析选用beautifulsoup库来处理;另外因为爬取时间较长所以可以引入smptlib,email来实现爬取完毕后自动发送邮件。

源码

一共三个脚本,第一个名字爬虫实现爬取网站上所有漫画的名字存入一个txt文档中以供图片爬虫调用,第二个图片爬虫脚本,第三个为实现发送邮件的脚本

get_names.py

# get_name.py
# 爬取主页所有漫画名字存入names.txt

import os
import time
from concurrent import futures

import requests
from bs4 import BeautifulSoup

# 请求一个url的函数,若请求失败,过2s重试,最多重试10次
def req_url(url):
    attempts = 0
    success = False
    while attempts < 10 and not success:
        try:
            r = requests.get(url=url, headers=headers)
            r.keep_alive = False
            success = True
            r.raise_for_status()
            return r
        except requests.exceptions.HTTPError:
            print('状态码非200!')
            return None
        except Exception:   # 若请求失败,过2s重试,最多重试10次
            time.sleep(2)
            attempts += 1
            print('****第{}次重连{}****'.format(attempts, url))
            if attempts == 10:
                print('连接失败! {}'.format(url))
                return None

# 取得一个页面内的所有漫画名并写入文件保存
def get_name(url):
    html = BeautifulSoup(req_url(url).text, 'lxml')
    for h5 in html.find_all('h5')[1:]:
        name = h5.a.get_text()
        print(name)
        with open('names.txt', 'a') as f:
            f.write(name)
            f.write('\n')

if __name__ == '__main__':
    # 设置最大重新连接次数
    requests.adapters.DEFAULT_RETRIES = 5
    # 域名
    top_url = 'https://www.xxxxx.com'	# 打码
    # 请求url列表
    urls = ['{}/page/{}'.format(top_url, i) for i in range(1, 33)]
    # 请求头
    headers = {
        'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:68.0) Gecko/20100101 Firefox/68.0'
    }
    # 8线程同时爬取
    with futures.ThreadPoolExecutor(max_workers=8) as pool:
        tasks = [pool.submit(get_name, url) for url in urls]

get_jpgs.py

# get_jpgs.py
# 爬取names.txt中的漫画

import os
import time
from concurrent import futures

import requests
from bs4 import BeautifulSoup

from send_email import send


# 请求一个url的函数,若请求失败,过2s重试,最多重试10次
def req_url(url):
    attempts = 0
    success = False
    while attempts < 10 and not success:
        try:
            r = requests.get(url=url, headers=headers)
            r.keep_alive = False
            success = True
            r.raise_for_status()
            return r
        except requests.exceptions.HTTPError:
            print('状态码非200!')
            urls_not200.append(url)
            return None
        except Exception:   # 若请求失败,过2s重试,最多重试10次
            time.sleep(2)
            attempts += 1
            print('****第{}次重连{}****'.format(attempts, url))
            if attempts == 10:
                print('连接失败! {}'.format(url))
                urls_fails.append(url)
                return None

# 取得当前漫画总页数
def get_page(name):
    name_url = '{}/中字h漫/{}/1'.format(url, name)
    r = req_url(name_url)
    if r is not None:
        html = BeautifulSoup(r.text, 'lxml')    # 解析html页面以找到页数
        select = html.find('select', id='single-pager')
        pages = (len(select.contents) - 1) // 2
        return pages
    return None



# 请求图片真实url并保存在本地
def get_jpg(name):
    stime = time.time()
    os.mkdir('imgs/{}'.format(name))
    pages = get_page(name)      # 取得当前漫画总页数
    if pages is not None:
        print('{} 总页数: {}'.format(name[0:6], pages))

        for page in range(1,pages+1):       # 遍历全部页码
            page_url = '{}/中字h漫/{}/1/p/{}'.format(url, name, page)
            r = req_url(page_url)
            if r is not None:
                html = BeautifulSoup(r.text, 'lxml')
                img = html.find('img',id='image-{}'.format(page-1))     # 取得图片所在标签
                jpg_url = img.attrs['data-src']         # 取得图片真实地址
                r = req_url(jpg_url)
                if r is not None:
                    jpg = r.content          # 下载图片
                    with open('imgs/{}/{}.jpg'.format(name,page), "wb")as f:   # 存入本地
                        f.write(jpg)
                    print('{} {}.jpg 保存完成!'.format(name[0:6],page))

        etime = time.time()
        print('**** ****')
        print('{}  全部保存完成,耗时 {:.2f}s'.format(name, etime-stime))
        print('**** ****')
        return name
    return None

if __name__ == '__main__':
    start_time = time.time()
    # 设置最大连接数
    requests.adapters.DEFAULT_RETRIES = 5
    # 域名,请求头
    url = 'https://www.xxxxx.com'	# 打码
    headers = {
        'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:68.0) Gecko/20100101 Firefox/68.0'
    }
    urls_not200 = []  # 保存状态码非200的url
    urls_fails = []  # 保存请求失败的url

    # 打开爬好的漫画名字文件并构建列表names
    with open('names.txt', 'r') as f:
        names = [line.rstrip('\n') for line in f]
    os.mkdir('imgs')

    # 线程池
    with futures.ThreadPoolExecutor(max_workers=25) as pool:    # 线程池,最大25个线程
        tasks = [pool.submit(get_jpg, name) for name in names]
        for task in futures.as_completed(tasks):
            print('----线程结束!----')

    time_sec = time.time() - start_time
    time_min = time_sec / 60
    time_hou = time_min / 60
    print('全部完成, 耗时 {:.2f}s! 即{:.2f}分钟,即{:.2f}小时'.format(time_sec, time_min, time_hou))
    send()
    print('urls_not200共{}个'.format(len(urls_not200)))
    print('urls_not200: ',urls_not200)
    print('urls_fails共{}个'.format(len(urls_fails)))
    print('urls_fails: ',urls_fails)

send_email.py

此脚本大部分参考 https://blog.csdn.net/LeoPhilo/article/details/89074232

以QQ邮箱为例,因为QQ 邮箱一般默认关闭SMTP服务,所以我们得先去开启它
python多线程爬取某网站全部H漫画_第3张图片

# smtplib 用于邮件的发信动作
import smtplib
from email.mime.text import MIMEText
# email 用于构建邮件内容
from email.header import Header
# 用于构建邮件头

def send():
    # 发信方的信息:发信邮箱,QQ 邮箱授权码
    from_addr = '[email protected]'
    password = 'xxx'

    # 收信方邮箱
    to_addr = '[email protected]'

    # 发信服务器
    smtp_server = 'smtp.qq.com'

    # 邮箱正文内容,第一个参数为内容,第二个参数为格式(plain 为纯文本),第三个参数为编码
    str = '爬取完毕,或者出错了!'
    msg = MIMEText(str, 'plain', 'utf-8')

    # 邮件头信息
    msg['From'] = Header(from_addr)
    msg['To'] = Header(to_addr)
    msg['Subject'] = Header('python test')

    # 开启发信服务,这里使用的是加密传输
    server = smtplib.SMTP_SSL(smtp_server)
    server.connect(smtp_server, 465)
    server = smtplib.SMTP_SSL(smtp_server)
    # 登录发信邮箱
    server.login(from_addr, password)
    # 发送邮件
    server.sendmail(from_addr, to_addr, msg.as_string())
    # 关闭服务器
    server.quit()

成果

python多线程爬取某网站全部H漫画_第4张图片名字太过se qing ,打码。
python多线程爬取某网站全部H漫画_第5张图片

总结

8说了,开冲!

你可能感兴趣的:(python)