import requests
from lxml import etree
from bs4 import BeautifulSoup
import re
from pyquery import PyQuery as pq
# 爬虫第一步:获取网页源代码
# 对指定url发送请求
class GetHtml(object):
"""
接收到url后使用requests模块发送请求
请求方式:默认get,可选择get/post
url的参数:可选arg,默认字典类型
"""
def __init__(self, url):
self.url = url
self.headers = {'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'}
def get_html(self, data_type='text', mode='get', arg={}, top=1):
"""如果mode为get,则准备发送get请求,判断arg是否为空来决定是否携带参数去发送请求"""
print(f"{(int(top)-1)*' '}正在爬取的网址是:{self.url} #################### 属于第{top}级页面")
# 判断mode,确定发送请求的方式
if mode == 'get':
# 判断是否需要携带参数发送get请求
if arg:
# 携带参数发送get请求
response = requests.get(url=self.url, headers=self.headers, params=arg)
else:
# 未携带参数发送get请求
response = requests.get(url=self.url, headers=self.headers)
# 判断mode,确定发送请求的方式
elif mode == 'post':
# 判断是否需要携带参数发送post请求
if arg:
# 携带参数发送post请求
response = requests.post(url=self.url, headers=self.headers, data=arg)
else:
# 未携带参数发送post请求
response = requests.post(url=self.url, headers=self.headers)
# 判断http响应码是否为200, 200表示http/https请求成功
if response.status_code == 200:
# 判断需要返回的数据类型是否为文本格式
if data_type == 'text':
return response.text
# 判断需要返回的数据类型是否为二进制格式
elif data_type == 'content':
return response.content
# 判断需要返回的数据类型是否为json格式
elif data_type == 'json':
return response.json()
# 聚焦爬虫第二步:解析网页源代码
# 解析html并以列表的形式返回数据
class Parser(object):
"""
传入html源码信息和相应的解析表达式,计算出对应的数据并以列表的形式返回
必选参数:html,解析表达式ex
解析方式默认为xpath,可选re/xpath/bs4/pyquery
"""
def __init__(self, html, mode='xpath'):
self.html = html
self.mode = mode
def parser(self, ex, flag=False, content=''):
if self.mode == 'xpath':
# 以xpath方式解析传入的html
e = etree.HTML(self.html)
data = e.xpath(ex)
elif self.mode == 'bs4':
# 以BeautifulSoup方式解析传入的html
soup = BeautifulSoup(self.html, 'lxml')
data = soup.select(ex)
elif self.mode == 're':
# 以正则方式解析传入的html
data = re.findall(ex, self.html)
elif self.mode == 'pyquery':
# 以pyquery方式解析传入的html
doc = pq(self.html)
data = doc(ex)
if flag:
urls = []
for i in data:
if not i.startswith('http'):
url = content + i
urls.append(i)
return urls
# 返回解析后的数据,数据类型为列表
return data
# 爬虫第三步:将获取到的数据存储为文件
# 将传入的数据存储到文件中
class SaveFile(object):
"""
将传入的数据存储到文件中
必选参数:file_path, data
写入模式:默认为w,可选w、a、wb等
"""
def save(self, file_path, data, mode='w'):
if mode == 'w' or mode == 'a':
# 以文本格式存储传入的数据
for date in data:
with open(file_path, mode, encoding='utf-8') as fp:
fp.write(date)
elif mode == 'wb' or mode == 'ab':
# 以二进制格式存储传入的数据
for date in data:
with open(file_path, mode) as fp:
fp.write(data)
# 爬虫之类级别写法:第四部分——Url管理器
class UrlManager(object):
"""url管理器:收集未爬取过的url,记录已经爬取过的url,并对待爬取的url列表进行去重"""
def __init__(self):
self.url_num = 0
self.urls = []
self.old_urls = []
# url去重
def remove_duplicates(self, urls):
"""除去urls中重复的值"""
# 用来存储处理后的每一个url
list1 = []
# 使用切片创建urls的副本,避免循环中删除urls的值后导致列表索引超出的错误
for i in urls[::]:
# 判断i是否已经在list1列表里了
if i not in list1:
# 如果i不在list1里面,则将i添加到list1里面
list1.append(i)
# 将查找并处理后的i从urls里删除
urls.remove(i)
# 返回处理后的urls,数据类型:列表
return list1
# 添加单个url到待爬的url列表中
def add_url(self, url):
"""将传入的url添加到待爬的url列表中"""
# 传入的url不能在已经爬取过的url列表中,也不能在还未爬取过的url的列表中
if url not in self.old_urls and url not in self.urls:
# 如果出入的url符合条件,则将传入的url添加到待爬取的url列表中
self.urls.append(url)
# 添加完url后将待爬取的url的数量+1
self.url_num += 1
# 添加多个url到待爬取的url列表中
def add_urls(self, urls):
"""将传入的多个url去重并添加到待爬取的url列表中"""
# 调用方法对传入的多个url进行去重处理
urls = self.remove_duplicates(urls)
for url in urls:
# 使用循环逐个调用方法添加url到待爬取的url列表中
self.add_url(url)
# 从待爬取的url列表中拿出一个url
def get_url(self):
"""从待爬取的url列表中获取一个url,并对url是否已经爬取了做记录"""
# 如果还有未爬取过的url,取出待爬取的url列表中的第一个url,并从待爬取的url列表中删除该url
if self.urls:
url = self.urls.pop(0)
# 将待爬取的url列表中url的总数量减1
self.url_num -= 1
# 如果已爬取过的url列表中没有url,那么将url添加到已爬取过的url列表中
if url not in self.old_urls:
self.old_urls.append(url)
return url
# 爬虫之类级别写法:第五部分——调度器
class Spider(object):
def __init__(self, url):
self.url = url
self.url_manager = UrlManager()
def start(self, content=''):
self.url_manager.add_url(self.url)
url = self.url_manager.get_url()
get_html = GetHtml(url)
html = get_html.get_html('text', 'get', arg={}, top=1)
ex = '//ul[@id="pins"]/li/a/@href'
parse = Parser(html=html, mode='xpath')
urls = parse.parser(ex, True)
self.url_manager.add_urls(urls)
save = SaveFile()
img_num = 1
while self.url_manager.url_num:
url = self.url_manager.get_url()
get_html = GetHtml(url)
html = get_html.get_html('text', 'get', top=2)
parse = Parser(html)
if len(self.url_manager.old_urls) < 24:
max_num = parse.parser('//div[@class="pagenavi"]/a[last()-1]/span/text()')[0]
for i in range(1, int(max_num)):
detail_url = url + f'/{i}'
self.url_manager.add_url(detail_url)
else:
src = parse.parser('//div[@class="main-image"]/p/a/img/@src')[0]
get_data = GetHtml(src)
get_data.headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
'referer': 'https://www.mzitu.com/'
}
img_data = get_data.get_html('content', 'get', top=3)
file_path = f'./image文件/meizi/{img_num}' + '.jpg'
save.save(file_path, img_data, mode='wb')
img_num += 1
if __name__ == "__main__":
# url = 'https://www.qiushibaike.com/text/'
url = 'https://www.mzitu.com/'
spider = Spider(url)
spider.start()
老师,我根据视频写了一个类似框架的爬虫类,出了一个大问题,我用它爬取妹子图网站的时候,爬取的速度非常慢,如果是糗图百科的段子就非常快,如何优化这个问题。我用这个框架爬取十张图片的时间都够我直接写线性代码爬完一整个图集了