项目分析
在python环境下使用多线程对妹子图网站的爬取;
爬取目标
爬取网站里面各个小组的图片,保存到本地;
使用工具
- python3.5;
- vscode;
- win10;
涉及模块
- requests、beautifulsoup、time、json、os、queue、threading、random
目标分析
首先,对主链接进行请求,获取各小组图片的链接,主链接get请求,返回HTML字符串,进行初级的数据提取,接着进行下一页信息提取。
其次,获取到url_list之后,针对每一页提取到图片的链接,以及下一页的url。
然后,获取到图片链接之后进行图片保存,以及下一张图片请求下载保存。
最后,将以上单线程拆分,建立三个队列,创建请求,提取,保存等功能,在主线程创造多线程,往线程内塞功能。
项目实施
代码部分
import requests
from bs4 import BeautifulSoup
import time
import json
import os
from queue import Queue
import threading
import random
class Mzitu_spider:
def __init__(self):
super().__init__()
self.url = '***********' # 爬取的主链接
self.header = {}
self.requests_session = requests.Session()
# 创建队列
self.url_queue = Queue()
self.response_queue = Queue()
self.img_save_queue = Queue()
self.user_agent_list = [
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 "
"(KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1",
"Mozilla/5.0 (X11; CrOS i686 2268.111.0) AppleWebKit/536.11 "
"(KHTML, like Gecko) Chrome/20.0.1132.57 Safari/536.11",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.6 "
"(KHTML, like Gecko) Chrome/20.0.1092.0 Safari/536.6",
"Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.6 "
"(KHTML, like Gecko) Chrome/20.0.1090.0 Safari/536.6",
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.1 "
"(KHTML, like Gecko) Chrome/19.77.34.5 Safari/537.1",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/536.5 "
"(KHTML, like Gecko) Chrome/19.0.1084.9 Safari/536.5",
"Mozilla/5.0 (Windows NT 6.0) AppleWebKit/536.5 "
"(KHTML, like Gecko) Chrome/19.0.1084.36 Safari/536.5",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 "
"(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
"Mozilla/5.0 (Windows NT 5.1) AppleWebKit/536.3 "
"(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_0) AppleWebKit/536.3 "
"(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 "
"(KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 "
"(KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 "
"(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 "
"(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/536.3 "
"(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 "
"(KHTML, like Gecko) Chrome/19.0.1061.0 Safari/536.3",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.24 "
"(KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24",
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/535.24 "
"(KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24",
]
def update_header(self, referer):
self.header['Referer'] = '{}'.format(referer)
self.header['User-Agent'] = '{}'.format(
random.choice(self.user_agent_list))
def get_url_list(self, url, url_list=[]):
# 获取主页各个图片组合的链接,包括名字
# 发送请求,获取页面信息
response = self.requests_session.get(url, headers=self.header)
time.sleep(0.5)
soup = BeautifulSoup(response.text, "lxml")
tag_span = soup.find_all("ul", attrs={"id":
"pins"})[0].find_all("span")
next_url = soup.find('a', attrs={"class": "next page-numbers"})
tag_a_list = []
# 获取页面图片名字及链接
for span in tag_span:
if span.find("a") is not None:
tag_a_list.append(span.find("a"))
for a in tag_a_list:
item = {}
item['href'] = a['href']
item['string'] = a.string
url_list.append(item)
print("添加成功")
# 处理下一页
if next_url is None:
return url_list
else:
print("下一页开始!!")
return self.get_url_list(url=next_url['href'], url_list=url_list)
def save_url(self, url_list):
# 保存url列表
with open("url.txt", "w+", encoding='utf-8') as f:
for content in url_list:
js = json.dumps(content, ensure_ascii=False)
f.write(js)
f.write("\n")
f.close()
def process_item(self):
# 提取数据线程
while True:
# 处理图片链接
response_item = self.response_queue.get()
soup = BeautifulSoup(response_item['response'].text, "lxml")
img_url = soup.find("img")["src"]
img_name = soup.find("h2", attrs={"class": "main-title"}).string
next_url = soup.find("div", attrs={
"class": "pagenavi"
}).find_all("span")[-1].string
if next_url == "下一页»":
next_url = soup.find("div", {
"class": "main-image"
}).find("a")["href"]
else:
next_url = None
# 保存图片
item = {}
item['img_url'] = img_url
item['img_name'] = img_name
item['referer'] = response_item['url']
self.img_save_queue.put(item)
if next_url is None:
print("一组图片下载完成")
self.response_queue.task_done()
else:
# 请求下一张图片网页并获取链接
print("开始下载下一张照片!")
self.update_header(response_item['url'])
response = self.requests_session.get(next_url,
headers=self.header)
time.sleep(3)
item = {}
item['response'] = response
item['url'] = next_url
self.response_queue.put(item)
self.response_queue.task_done()
def get_details_page_item(self):
# 获取详情页图片
while True:
url = self.url_queue.get()
# 发送请求之前添加referer请求头
# 发送请求,获取响应
self.update_header(self.url)
response = self.requests_session.get(url, headers=self.header)
time.sleep(3)
item = {}
item['response'] = response
item['url'] = url
# 交给下一个函数处理
# 返回图片下载链接,及下一张图片网页链接
self.response_queue.put(item)
self.url_queue.task_done()
def save_img(self):
# 保存图片
while True:
img_item = self.img_save_queue.get()
suffix = os.path.splitext(img_item['img_url'])
file_name = img_item['img_name'] + suffix[1]
time.sleep(1)
with open(".\\imgs\\" + file_name, "wb") as f:
self.update_header(img_item['referer'])
f.write(
self.requests_session.get(img_item['img_url'],
headers=self.header).content)
f.close()
print("成功保存一张张片")
self.img_save_queue.task_done()
def run(self):
# 图片组合的链接列表
# 获取并保存所有图片组合的链接
# url_list = self.get_url_list(self.url)
# self.save_url(url_list)
# 打开保存的链接文件
with open(".\\url.txt", "r", encoding='utf-8') as f:
lines = f.readlines()
item_list = []
for line in lines:
dic_line = json.loads(line)
item_list.append(dic_line)
f.close()
for url in item_list:
self.url_queue.put(url['href'])
# 调试使用
# 可以针对某个单独的链接进行爬取
# self.url_queue.put('https://www.mzitu.com/223616')
# url列表提取完成
print("*" * 30 + "准备加载图片!")
thread_list = []
# 发送请求获取响应
for i in range(4):
t_get = threading.Thread(target=self.get_details_page_item)
thread_list.append(t_get)
# 提取数据
t_html = threading.Thread(target=self.process_item)
thread_list.append(t_html)
# 保存照片
t_save = threading.Thread(target=self.save_img)
thread_list.append(t_save)
for t in thread_list:
t.setDaemon(True) # 把子线程设置为守护线程
t.start()
for q in [self.url_queue, self.response_queue, self.img_save_queue]:
q.join() # 让主线程堵塞,等待其他线程
print("主线程结束!,爬取完成。")
if __name__ == '__main__':
mzitu = Mzitu_spider()
mzitu.run()
项目结果
项目结果这里,还是算了吧不然估计要审核不通过了,对于图片结果大家还是走代码看吧,毕竟要科学上网,文明编程嘛,哈哈!!!,主要是怕火!!!
项目总结
首先项目注意点这块,还是非常有必要说的,在写这个小项目的时候,也是遇到很多的小挫折,比如在跑程序的时候每发送一个请求都要更换请求头尤其是referer,因为在你请求图片的时候,网站会检测,如果请求头只加了一个User_Agent是不得行的,所有小编觉得不断变换请求头太麻烦就使用了requests.Session方法,当然应该也可以直接用get请求,这里我没有试过,嫌麻烦就直接用的这个方法。还有在添加线程之后最好加一些反爬信息,如果有条件直接给挂一个代理IP,因为这个网站现在开始有反爬机制了,在这里我只设置了随机User_Agent,而且在代码中我也在请求的时候让程序睡几秒,由于只是学习为目的,所以没必要让它这么快,但是如果有条件的读者可以直接挂一个代理IP接口,随心所欲啦,哈哈!!!
其次,在代码中run方法里面,先把头两行注释打开获取url_list,再接着塞到队列里面,当然也可以不用保存到本地,只需要稍改run方法即可。
最后,声明以上内容仅作为学习使用,毕竟有的爬虫还是违反道德的嘛,希望大家可以喜欢,学艺不精,还请海涵!!!