该爬虫功能如下:
1.可对网站进行进行翻页操作,并搜索获取与关键字匹配的链接。
2.对网页图片搜索并下载下来。
3.对下载下来的图片可使用文件格式过滤或者文件大小过滤。
4.对于当前下载的图片会创建名为'images'的文件夹,稍后以 “关键字” + “_” + 任务ID的形式命名新文件夹。
5.在搜索链接完毕后会创建以pkl为后缀,关键字为名称的文件用于保存任务,当用户在控制台输入相同的关键字时,便会进行搜索。若搜索到该本件便会询问用户是否加载,若否,程序也并不会直接删除pkl文件而是在第二次搜索链接完毕后直接覆盖。当所有任务完成时(包括由于超过重试次数而跳过的链接),会删除pkl文件。
该爬虫需要注意的地方:
1.该爬虫最初编写的时候只是想要能下载某一网页的图片,后面加需求再变成这样的,有些地方可能看起来写的莫名奇妙。
2.该爬虫没有设置访问频率,需要自行添加。(可以直接import time,然后time.sleep()就行了,笔者以高频率爬取了某网站4万张图片,成功被网站拉黑了)
3.如果出现特殊情况,某一任务未成功下载,可将pkl文件的workPoint重写。(该代码并不会删除pkl文件中的任何链接,只是会修改workPoint)
4.用到了外部库,可以用一下命令进行安装。
pip install beautifulsoup4
pip install requests
5.虽然说是可以下载大量图片,但是几十万张图片及以上的量该爬虫应该是不能够胜任的。个人认为代码运行时间越长就越应该考虑各种各样的情况,但该代码并不能处理这些可能的情况(当然一般人也不会需要爬取这么多图片,需要爬这么多图片的人也看不上咱这代码。。。)
6.该爬虫并不能处理动态访问的网站。
7.该代码重新加载pkl文件后,对于上一次下载遗留下来images文件夹,处理里面的内容的做法并不是接着上一个图片去下载,而是删除images文件夹中的所有内容然后重头开始下载。
8.过滤的图片的手段并不是直接在爬取前识别文件,而是下载下来后识别再删除。
9.对于不同的网站搜索链接部分的代码可能需要不同程度上的修改。
import os
import requests
from bs4 import BeautifulSoup
from urllib.parse import urljoin
from base64 import b64decode
import pickle
#根网址
Baseurl = 'https://hd.wve7n.com/2046/'
#初始页面/当前页面
nowDepth = 80
#存储所有链接的列表
targeturl_list = []
#对完成的文件夹计数
haveDownloaded = 0
#对于失败请求,最多重试5次
trytimes = 5
#网站响应时间
waittime = 8
#标记当前处理到列表的哪一个元素
workPoint = 0
#总任务数
totalTask = 0
#用于存档文件
orginal_list = []
#用于获取关键字匹配的链接
def SearchSection(targetword, pageDepth):
global nowDepth
global targeturl_list
global totalTask
global orginal_list
while nowDepth <= pageDepth:
url = f'https://hd.wve7n.com/2046/thread.php?fid=278&page={nowDepth}'
# 发送HTTP请求获取网页内容
isresponse = False
times = 0
while not isresponse:
try:
response = requests.get(url, timeout=waittime)
print(f"第{nowDepth}页网页成功响应")
isresponse = True
except requests.exceptions.RequestException as e:
times += 1
print(f'请求异常第{times}次:{e}')
if times >= trytimes:
print(f'请求失败,已达到最大重试次数')
break
#当5次请求都失败时,直接跳过该页
if not isresponse:
nowDepth += 1
continue
#确保请求成功
if response.status_code == 200:
#使用BeautifulSoup解析网页内容
soup = BeautifulSoup(response.text, 'html.parser')
#统计找到的链接数
thisurl = 0
#查找所有<a>标签
for link in soup.find_all('a'):
#获取链接的href属性,即链接地址
href = link.get('href')
#获取链接的文本内容
text = link.get_text()
#检查链接文本中是否包含特定关键词
if targetword in text:
thisurl += 1
print(f'找到包含{targetword}的链接:{Baseurl+href}')
targeturl_list.append(Baseurl+href)
else:
print('网页请求获取失败')
if isresponse == True:
print(f'第{nowDepth}页url搜索完毕')
if thisurl == 0:
print(f"该页未发现包含“{targetword}”的链接")
#页面搜索完毕,准备进入下一页
nowDepth += 1
print(targeturl_list)
print(f'检索到{targeturl_list.__len__()}个包含{targetword}的链接')
#创建链接任务列表的副本用于存档和方便后续代码编写
orginal_list = targeturl_list.copy()
totalTask = targeturl_list.__len__()
#保存列表
with open(f'{targetword}_task.pkl', 'wb') as f:
pickle.dump((orginal_list, workPoint), f)
def DownloadSection(filtertype, targetword):
global workPoint
global haveDownloaded
global workPoint
global totalTask
global orginal_list
for eachurl in targeturl_list:
#目标网站URL
url = eachurl
#请求头
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3'
}
isresponse = False
times = 0
while not isresponse:
try:
#发送HTTP请求
response = requests.get(url, headers=headers, timeout=waittime)
isresponse = True
except requests.exceptions.RequestException as e:
times += 1
print(f'请求异常第{times}次:{e}')
if times >= trytimes:
print(f'请求失败,已达到最大重试次数')
break
#确保请求成功
if not isresponse:
print('跳过当前url地址')
continue
if os.path.exists('images') == False:
os.mkdir('images')
#检查请求是否成功
if response.status_code == 200:
picnum = 0
#使用BeautifulSoup解析HTML内容
soup = BeautifulSoup(response.text, 'html.parser')
#查找所有的<img>标签
img_tags = soup.find_all('img')
for img in img_tags:
#获取<img>标签的src属性
img_url = img.get('src')
#如果src属性为相对路径,将其转换为绝对路径
if not img_url.startswith(('http:', 'https:', 'data:')):
img_url = urljoin(url, img_url)
#处理数据 URI
if img_url.startswith('data:'):
isresponse = False
times = 0
while not isresponse:
try:
#从数据 URI 中提取 base64 编码的图片数据
_, img_data = img_url.split(',', 1)
#解码 base64 图片数据
img_data = b64decode(img_data)
#生成图片名称
img_name = 'image_base64.png'
isresponse = True
picnum += 1
print(f"图片下载进度:({picnum}/{img_tags.__len__()})")
except Exception:
times += 1
print(f"下载图片失败:{img_url}(第{times}次)")
if times >= trytimes:
print(f'无法下载图片,已达到最大重试次数')
break
else:
isresponse = False
times = 0
while not isresponse:
try:
#获取图片的二进制内容
img_data = requests.get(img_url, headers=headers, timeout=10).content
#获取图片名称
img_name = os.path.basename(img_url)
isresponse = True
picnum += 1
print(f"图片下载进度:({picnum}/{img_tags.__len__()})")
except Exception:
times += 1
print(f"下载图片失败:{img_url}(第{times}次)")
if times >= trytimes:
print(f'无法下载图片,已达到最大重试次数')
break
#检查是否获取到图片数据
if isresponse == False:
continue
#保存图片
with open(f'images/{img_name}', 'wb') as f:
f.write(img_data)
print(f"图片已保存:{img_name}")
else:
print(f"请求失败,状态码:{response.status_code}")
#多余的杂图删除
path = 'images'
#用于根据文件格式过滤图片
extensions = ('png', 'gif')
#统计删除图片数
removenum = 0
#根据文件格式删除
if filtertype == '1':
for filename in os.listdir(path):
file_path = os.path.join(path, filename)
if os.path.isfile(file_path) and filename.endswith(extensions):
os.remove(file_path)
removenum += 1
print(f"已删除文件:{filename}")
#根据文件大小删除
else:
for filename in os.listdir(path):
file_path = os.path.join(path, filename)
#硬编码:当文件大小小于100k时,删除该文件
if os.path.isfile(file_path) and os.path.getsize(file_path) < 102400:
os.remove(file_path)
removenum += 1
print(f"已删除文件:{filename}")
print(f"已删除文件数量:{removenum}")
print(f"实际保留有效图片数:{img_tags.__len__() - removenum}")
#依据任务进度,重命名文件夹
haveDownloaded += 1
workPoint = haveDownloaded
new_name = targetword + '_' + f'{haveDownloaded}'
if os.path.exists(path):
os.rename(path, new_name)
print(f"文件夹已重命名为“{new_name}”")
else:
print("文件夹不存在")
print(f"{url}中图片下载完成")
#统计下载情况
#刷新任务文件
with open(f'{targetword}_task.pkl', 'wb') as f:
pickle.dump((orginal_list, workPoint), f)
print(f"任务进程情况({haveDownloaded}/{totalTask})")
print(f"已完成{int(haveDownloaded / totalTask * 100_00)/100}%")
print("--------------------------------------")
if os.path.exists(f'{targetword}_task.pkl'):
os.remove(f'{targetword}_task.pkl')
if __name__ == '__main__':
# 获取关键词
targetword = input('请输入关键词:')
asking = '0'
if os.path.exists(f'{targetword}_task.pkl'):
asking = input('检测到已存在任务文件,是否加载?\n1:是;\n2:否')
#加载任务文件事件
if asking == '1':
#图片过滤方式
filterType = input('请输入图片过滤方式:\n1:文件格式过滤;\n2:文件大小过滤')
with open(f'{targetword}_task.pkl', 'rb') as f:
targeturl_list, workPoint = pickle.load(f)
totalTask = targeturl_list.__len__()
print(f"任务进程情况({workPoint}/{totalTask})")
print(f"上次已完成{int(workPoint / totalTask * 100_00)/100}%")
if workPoint >= 1:
targeturl_list = targeturl_list[workPoint:]
#统一数值
haveDownloaded = workPoint
if os.path.exists('images') and os.path.isdir('images'):
for filename in os.listdir('images'):
file_path = os.path.join('images', filename)
try:
# 如果是文件,则删除
if os.path.isfile(file_path) or os.path.islink(file_path):
os.unlink(file_path)
# 如果是文件夹,则递归删除
elif os.path.isdir(file_path):
os.rmdir(file_path)
except Exception :
print(f"删除文件夹'images' 中的文件或子文件夹时出错")
DownloadSection(filtertype=filterType, targetword=targetword)
#不加载任务文件事件
elif asking == '2':
#最终页面
pageDepth = int(input('请输入搜索页数:'))
#图片过滤方式
filterType = input('请输入图片过滤方式:\n1:文件格式过滤;\n2:文件大小过滤')
targeturl_list = []
SearchSection(targetword=targetword, pageDepth=pageDepth)
DownloadSection(filtertype=filterType, targetword=targetword)
else:
pass
#无任务文件事件
if asking == '0':
#最终页面
pageDepth = int(input('请输入搜索页数:'))
#图片过滤方式
filterType = input('请输入图片过滤方式:\n1:文件格式过滤;\n2:文件大小过滤')
SearchSection(targetword=targetword, pageDepth=pageDepth)
DownloadSection(filtertype=filterType, targetword=targetword)
(主要是做做分享。。。)