首发于个人博客(客官大人来这里看啊!):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标签中,如图
如图的img标签中的src属性便是图片的真实url,只要请求这个url便可卸载此张图片。
因为每一本漫画的所有页url都是按规律的,所以只要得到每本漫画的总页数便可构造他全部页的url,进而得到每一个图片的真实url,进行下载。
分析发现,漫画的每一个页面中都有一个快速选择页数的小控件,如图
分析html页面找到其对应的标签:
因此,只要找到这个标签便可解析得到总页数。
以上是针对如何爬取一本漫画的所有图片,若要爬取所有页面,只需先写一个爬虫把所有网站上所有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_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
# 爬取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)
此脚本大部分参考 https://blog.csdn.net/LeoPhilo/article/details/89074232
以QQ邮箱为例,因为QQ 邮箱一般默认关闭SMTP服务,所以我们得先去开启它
# 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()
8说了,开冲!