前言
分享一篇使用部分爬虫技术简单实现对媒体类网页按需求爬取文章并保存到本地指定文件夹的案例,仅供相关学习者参考,学习过程切勿对网站频繁访问而造成网页瘫痪,量力而为!!!
爬取需求
爬取地址:建筑档案-建筑行业全产业链内容共建平台
爬取涉及技术:Requests(访问)、Xpath(解析)、Pandas(保存)
爬取目的:仅用于练习巩固爬虫技术
爬取目标:
- 将网页首页中每个文章的标题、作者、发布时间、文章链接保存到指定位置的excel文件中,excel名称为data
- 将每个文章的文本保存到指定位置的文档中,并以文章标题命名
- 将每个文章的图片保存到指定位置的文件夹中,并以文章标题 + 图片序号命名
爬取思路
发起首页网页请求,获取响应内容
解析首页网页数据,获取文章的标题、作者、发布时间、文章链接信息
汇总网站首页信息,保存到excel中
发起文章网页请求,获取响应内容
解析文章网页数据,获取文章的文本、图片链接信息
保存文章内容到指定文档中
发起图片网页请求,获取响应内容
保存图片到指定文件夹中
代码讲解
下面以爬虫思路的顺序讲解每个步骤的实现方式以及注意事项
步骤一:导入相关库
import requests # 用于发起网页请求,返回响应内容
from lxml import etree # 对响应内容解析数据
import pandas as pd # 生成二维数据,保存到excel中
import os # 文件保存
import time # 减慢爬虫速度
没有相关的库就自行安装
win键+r,调出CMD窗口,输入以下代码
pip install 欠缺的库名
步骤二:设置防识别爬虫措施
"""
记录爬虫时间
设置访问网页基础信息
"""
start_time = time.time()
start_url = "https://www.jzda001.com"
header = {'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36'}
hearder:表头,自己复制浏览器的表头,实在不会找就直接复制我的用
步骤三:主页访问,解析响应内容
# 主页面访问 + 解析信息
response = requests.get(start_url, headers=header) # 发起请求
html_str = response.content # 对响应内容转码
tree = etree.HTML(html_str) # 解析响应内容
# 创建字典,用于装载信息
dict = {}
# 获取文章链接
dict['href_list'] = tree.xpath("//ul[@class='art-list']//a[1]/@href")[1:]
# 获取文章标题
dict['title_list'] = tree.xpath("//div[contains(@class,'title')]/p[contains(@class,'line')]//text()")[:-1]
# 获取文章作者
dict['author_list'] = tree.xpath("//div[@class='author']/p[@class='name']//text()[1]")[:-2]
# 获取文章发布时间
dict['time_list'] = tree.xpath("//div[@class='author']/p[@class='name']//text()[2]")[:-1]
# 暂停3秒
time.sleep(3)
自行根据网络情况调整time.sleep(),设置是主要为了防止爬太快
解析过程发现获取的内容头部或尾部有不需要的信息,因此使用列表切片方式将元素剔除掉
xpath解析方式,参考另一篇文章:【Xpath】基于lxml库的爬虫解析汇总
步骤四:数据清洗
# 主页面数据清洗
author_clear = []
for i in dict['author_list']:
if i != '.':
author_clear.append(i.replace('\n', '').replace(' ', ''))
dict['author_list'] = author_clear
time_clear = []
for j in dict['time_list']:
time_clear.append(j.replace('\n', '').replace(' ', '').replace(':', ''))
dict['time_list'] = time_clear
title_clear = []
for n in dict['title_list']:
title_clear.append(n.replace(':', ' ').replace('!', ' ').replace('|', ' ').replace('/', ' ').replace(' ', ' '))
dict['title_list'] = title_clear
time.sleep(3)
查看网页结构,可以看到标题、作者、发布时间都有一些系统敏感字符(影响后续无法创建文件夹)
所以,需要把解析到的数据作数据清洗后才能使用
步骤五:创建文章链接
# 生成子页面地址
url_list = []
for j in dict['href_list']:
url_list.append(start_url + j)
dict['url_list'] = url_list
通过对比网页结构的@href属性链接和文章的真实链接,可知解析到的url需要拼接网址首页进去
步骤六:访问文章链接并解析响应内容
# 子页面解析
dict_url = {} # 用于装载文章的解析信息
for n in range(len(url_list)): # 循环遍历单个子页面
response_url = requests.get(url_list[n], headers=header, timeout=5)
response_url.encoding = 'utf-8'
tree_url = etree.HTML(response_url.text)
# 获取文章文本信息
text_n = tree_url.xpath("//div[@class='content']/p/text()")
# 拼接文本内容
dict_url['text'] = [txt + txt for txt in text_n]
# 获取文章中所有图片的链接
dict_url['img_url'] = tree_url.xpath("//div[@class='pgc-img']/img/@src")
# 获取该文章的标题
title_n = dict['title_list'][n]
# 获取该文章的作者信息
author_n = dict['author_list'][n]
# 获取该文章的发布时间
time_n = dict['time_list'][n]
拼接文本内容是因为获取到的文本信息是一段一段分开的,需要拼接在一起,才是完整的文章文本信息
# 获取该文章的标题
title_n = dict['title_list'][n]
# 获取该文章的作者信息
author_n = dict['author_list'][n]
# 获取该文章的发布时间
time_n = dict['time_list'][n]
因为首页的信息和文章访问的顺序是一致的,所以循环次数 = 该文章的信息在字典中的位置
步骤七:创建多层文件夹
# 新建多层文件夹
file_name = "建筑档案爬虫文件夹" # 文件夹名称
path = r'd:/' + file_name
url_path_file = path + '/' + title_n + '/' # 拼接文件夹地址
if not os.path.exists(url_path_file):
os.makedirs(url_path_file)
因为创建的文件夹是:“./爬虫文件夹名称/文章标题名称/”,所以需要用os.makedirs来创建多层文件夹
注意文件名称最后带 “/”
步骤八:保存子页面图片
当建立好文件夹后,就开始访问每个文章的图片链接,并且保存下来
# 保存子页面图片
for index, item in enumerate(dict_url['img_url']):
img_rep = requests.get(item, headers=header, timeout=5) # 设置超时时间
index += 1 # 每保存图片一次,显示数量+1
img_name = title_n + ' 图片' + '{}'.format(index) # 图片名称
img_path = url_path_file + img_name + '.png' # 图片地址
with open(img_path, 'wb') as f:
f.write(img_rep.content) # 以二进制的方式写入
f.close()
print('第{}张图片保存成功,保存地址为:'.format(index), img_path)
time.sleep(2)
步骤九:保存子页面文本
先保存文本再保存图片也可以,需要注意两者的写入方式
# 保存网页文本
txt_name = str(title_n + ' ' + author_n + ' ' + time_n) # 文本名称
txt_path = url_path_file + '/' + txt_name + '.txt' # 文本地址
with open(txt_path, 'w', encoding='utf-8') as f:
f.write(str(dict_url['text']))
f.close()
print("该页面文本保存成功,文本地址为:", txt_path)
print("爬取成功,正在爬取第{}个页面!!!".format(n + 2))
print('\n')
time.sleep(10)
步骤十:文章信息汇总
将网站首页的所有文章标题、作者、发布时间、文章链接都保存到一个excel文件夹中
# 主页面信息保存
data = pd.DataFrame({'title': dict['title_list'],
'author': dict['author_list'],
'time': dict['time_list'],
'url': dict['url_list']})
data.to_excel('{}./data.xls'.format(path),index=False)
print('保存成功,excel文件地址:', '{}/data.xls'.format(path))
完整代码(面向过程版)
# !/usr/bin/python3.9
# -*- coding:utf-8 -*-
# @author:inganxu
# CSDN:inganxu.blog.csdn.net
# @Date:2021年12月18日
import requests # 用于发起网页请求,返回响应内容
from lxml import etree # 对响应内容解析数据
import pandas as pd # 生成二维数据,保存到excel中
import os # 文件保存
import time # 减慢爬虫速度
"""
记录爬虫时间
设置访问网页基础信息
"""
start_time = time.time()
start_url = "https://www.jzda001.com"
header = {'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36'}
# 主页面访问 + 解析信息
response = requests.get(start_url, headers=header) # 发起请求
html_str = response.content # 对响应内容转码
tree = etree.HTML(html_str) # 解析响应内容
# 创建字典,用于装载信息
dict = {}
# 获取文章链接
dict['href_list'] = tree.xpath("//ul[@class='art-list']//a[1]/@href")[1:]
# 获取文章标题
dict['title_list'] = tree.xpath("//div[contains(@class,'title')]/p[contains(@class,'line')]//text()")[:-1]
# 获取文章作者
dict['author_list'] = tree.xpath("//div[@class='author']/p[@class='name']//text()[1]")[:-2]
# 获取文章发布时间
dict['time_list'] = tree.xpath("//div[@class='author']/p[@class='name']//text()[2]")[:-1]
# 暂停3秒
time.sleep(3)
# 主页面数据清洗
author_clear = []
for i in dict['author_list']:
if i != '.':
author_clear.append(i.replace('\n', '').replace(' ', ''))
dict['author_list'] = author_clear
time_clear = []
for j in dict['time_list']:
time_clear.append(j.replace('\n', '').replace(' ', '').replace(':', ''))
dict['time_list'] = time_clear
title_clear = []
for n in dict['title_list']:
title_clear.append(n.replace(':', ' ').replace('!', ' ').replace('|', ' ').replace('/', ' ').replace(' ', ' '))
dict['title_list'] = title_clear
time.sleep(3)
# 生成子页面地址
url_list = []
for j in dict['href_list']:
url_list.append(start_url + j)
dict['url_list'] = url_list
# 子页面解析
dict_url = {} # 用于装载文章的解析信息
for n in range(len(url_list)): # 循环遍历单个子页面
response_url = requests.get(url_list[n], headers=header, timeout=5)
response_url.encoding = 'utf-8'
tree_url = etree.HTML(response_url.text)
# 获取文章文本信息
text_n = tree_url.xpath("//div[@class='content']/p/text()")
# 拼接文本内容
dict_url['text'] = [txt + txt for txt in text_n]
# 获取文章中所有图片的链接
dict_url['img_url'] = tree_url.xpath("//div[@class='pgc-img']/img/@src")
# 获取该文章的标题
title_n = dict['title_list'][n]
# 获取该文章的作者信息
author_n = dict['author_list'][n]
# 获取该文章的发布时间
time_n = dict['time_list'][n]
# 新建多层文件夹
file_name = "建筑档案爬虫文件夹" # 文件夹名称
path = r'd:/' + file_name
url_path_file = path + '/' + title_n + '/' # 拼接文件夹地址
if not os.path.exists(url_path_file):
os.makedirs(url_path_file)
# 保存子页面图片
for index, item in enumerate(dict_url['img_url']):
img_rep = requests.get(item, headers=header, timeout=5) # 设置超时时间
index += 1 # 每保存图片一次,显示数量+1
img_name = title_n + ' 图片' + '{}'.format(index) # 图片名称
img_path = url_path_file + img_name + '.png' # 图片地址
with open(img_path, 'wb') as f:
f.write(img_rep.content) # 以二进制的方式写入
f.close()
print('第{}张图片保存成功,保存地址为:'.format(index), img_path)
time.sleep(2)
# 保存网页文本
txt_name = str(title_n + ' ' + author_n + ' ' + time_n) # 文本名称
txt_path = url_path_file + '/' + txt_name + '.txt' # 文本地址
with open(txt_path, 'w', encoding='utf-8') as f:
f.write(str(dict_url['text']))
f.close()
print("该页面文本保存成功,文本地址为:", txt_path)
print("爬取成功,正在爬取第{}个页面!!!".format(n + 2))
print('\n')
time.sleep(10)
# 主页面信息保存
data = pd.DataFrame({'title': dict['title_list'],
'author': dict['author_list'],
'time': dict['time_list'],
'url': dict['url_list']})
data.to_excel('{}./data.xls'.format(path),index=False)
print('保存成功,excel文件地址:', '{}/data.xls'.format(path))
print('爬取完成!!!!')
end_time = time.time()
print('爬取所用时长:', end_time - start_time)
完整代码(面向对象版)
# !/usr/bin/python3.9
# -*- coding:utf-8 -*-
# @author:inganxu
# CSDN:inganxu.blog.csdn.net
# @Date:2021年12月18日
import requests # 用于发起网页请求,返回响应内容
from lxml import etree # 对响应内容解析数据
import pandas as pd # 生成二维数据,保存到excel中
import os # 文件保存
import time # 减慢爬虫速度
class JZDA:
def __init__(self):
self.start_url = "https://www.jzda001.com"
self.header = {'user-agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36'}
# 发送请求,返回响应
def parse_url(self, url):
response = requests.get(url, headers=self.header)
return response.content
# 解析主页面数据
def get_url_content(self, html_str):
dict = {}
tree = etree.HTML(html_str)
dict['href_list'] = tree.xpath("//ul[@class='art-list']//a[1]/@href")[1:]
dict['title_list'] = tree.xpath("//div[contains(@class,'title')]/p[contains(@class,'line')]//text()")[:-1]
dict['author_list'] = tree.xpath("//div[@class='author']/p[@class='name']//text()[1]")[:-2]
dict['time_list'] = tree.xpath("//div[@class='author']/p[@class='name']//text()[2]")[:-1]
dict['url_list'], dict['title_list'], dict['author_list'], dict['time_list'] = self.data_clear(dict)
return dict
# 数据清洗
def data_clear(self, dict):
# 生成子页面
url_list = []
for href in dict['href_list']:
url_list.append(self.start_url + href)
# 数据清洗
author_clear = []
for i in dict['author_list']:
if i != '.':
author_clear.append(i.replace('\n', '').replace(' ', ''))
author_list = author_clear
time_clear = []
for j in dict['time_list']:
time_clear.append(j.replace('\n', '').replace(' ', '').replace(':', ''))
time_list = time_clear
title_clear = []
for n in dict['title_list']:
title_clear.append(
n.replace(':', ' ').replace('!', ' ').replace('|', ' ').replace('/', ' ').replace(' ', ' '))
title_list = title_clear
return url_list, title_list, author_list, time_list
# 获取子页面的的标题、作者、时间
def parse_content_dict(self, dict, index):
dict_index = {}
dict_index['title'] = dict['title_list'][index]
dict_index['author'] = dict['author_list'][index]
dict_index['time'] = dict['time_list'][index]
return dict_index
# 获取子页面的文本和图片链接
def get_html_url(self, url):
dict_url = {}
# 发送请求
response_url = requests.get(url, headers=self.header, timeout=3)
response_url.encoding = 'utf-8'
# 解析子页面
tree_url = etree.HTML(response_url.text)
# 获取文本
text = tree_url.xpath("//div[@class='content']/p/text()")
# 获取图片
dict_url['img_url'] = tree_url.xpath("//div[@class='pgc-img']/img/@src")
dict_url['text'] = [txt + txt for txt in text]
return dict_url
# 保存子页面文本
def save_url_text(self, path, dict_index, dict_url, index):
txt_name = str(dict_index['title'] + ' ' + dict_index['author'] + ' ' + dict_index['time'])
txt_path = path + '/' + txt_name + '.txt'
with open(txt_path, 'w', encoding='utf-8') as f:
f.write(str(dict_url['text']))
f.close()
print("该页面文本保存成功,文本地址为:", txt_path)
print("爬取成功,正在爬取第{}个页面!!!".format(index + 2))
print('\n')
time.sleep(10)
# 保存子页面图片
def save_url_img(self, img_index, img_url, dict_index, path):
# 请求访问
img_rep = requests.get(img_url, headers=self.header, timeout=5) # 把图片转成二进制
img_index += 1
img_name = dict_index['title'] + ' 图片' + '{}'.format(img_index)
img_path = path + img_name + '.png'
with open(img_path, 'wb') as f:
f.write(img_rep.content)
f.close()
print('第{}张图片保存成功,保存地址为:'.format(img_index), img_path)
time.sleep(2)
# 保存主页面信息
def save_content_list(self, dict, file_name):
data = pd.DataFrame({'title': dict['title_list'],
'author': dict['author_list'],
'time': dict['time_list'],
'url': dict['url_list']})
# 指定保存文本位置
data.to_excel('{}./data.xls'.format(r'd:/' + file_name), index=False)
print('保存成功,excel文件地址:', '{}/data.xls'.format(r'd:/' + file_name))
def run(self):
start_time = time.time()
next_url = self.start_url
# 发送请求,获取响应
html_str = self.parse_url(next_url)
# 解析主页面数据,获取标题、作者、时间、子页面链接
dict = self.get_url_content(html_str)
# 建立爬虫信息保存文件夹
file_name = "建筑档案爬虫文件夹"
# 遍历子页面数据
for index, url in enumerate(dict['url_list']):
# 获取子页面的文本和图片链接,并生成字典
dict_url = self.get_html_url(url)
# 获取子页面的标题、作者、时间,并生成字典
dict_index = self.parse_content_dict(dict, index)
# 创建子页面信息保存地址,命名规则:文件夹+标题
path = r'd:/' + file_name + '/' + dict_index['title'] + '/'
if not os.path.exists(path):
os.makedirs(path)
# 将子页面文本以标题-作者-时间命名到文件夹中
self.save_url_text(path, dict_index, dict_url, index)
# 遍历子页面的图片链接
for img_index, img_url in enumerate(dict_url['img_url']):
# 将子页面图片以标题 图片号命名到文件夹中
self.save_url_img(img_index, img_url, dict_index, path)
# 将标题、作者、时间、子页面链接保存到csv中
self.save_content_list(dict, file_name)
print("爬取完成")
end_time = time.time()
print('爬取所用时长:', end_time - start_time)
if __name__ == '__main__':
jzda = JZDA()
jzda.run()
结语
后续有时间再对该案例扩展防反爬措施(ip池、表头池、响应访问)、数据处理(数据分析、数据可视化)等内容