前言
分享一篇使用部分爬虫技术简单实现对媒体类网页按需求爬取文章并保存到本地指定文件夹的案例,仅供相关学习者参考,学习过程切勿对网站频繁访问而造成网页瘫痪,量力而为!!!
爬取需求
爬取地址:建筑档案-建筑行业全产业链内容共建平台
爬取涉及技术:Scrapy(访问)、Xpath(解析)
爬取目的:仅用于练习巩固爬虫技术
爬取目标:
爬取“推荐文章”、“专题推荐”、“视频推荐”栏目下,纵向分布的文章
保存文章的图片、文本到指定文件夹中
图片命名格式:文章标题 + 图片序号命名
文本命名格式:文章标题
文档命名:data
爬取思路
请求访问主页面,解析返回的响应,获取“推荐文章”、“专题推荐”、“视频推荐”栏目下,纵向分布的文章的标题、作者、发布时间、文章链接
迭代访问文章链接,获取文章的文本、图片链接
请求文章图片链接,保存图片到指定文件夹中
保存文章的文本到指定文件夹中
目标示例
代码讲解
下面以爬虫思路的顺序讲解每个步骤的实现方式以及注意事项
步骤一:新建爬虫项目
【Scrapy】安装与基础配置笔记_inganxu-CSDN博客
关于新建爬虫部分就直接跳过了,要是不清楚不清楚如何新建爬虫项目就自行查看CSDN其他大佬的帖子,下面直接上爬虫部分
步骤二:items.py(定义数据储存的名称)
scrapy.Field()函数用于定义信息标签
根据爬虫需求,定义所需的信息标签(文章链接、文章标题、文章作者、发布时间、图片链接、文章内容、文件地址、文章序号、图片序号)
步骤三:spider.py(定义爬虫内容)
由于爬虫需求为纵向排列的文章,并不需要主页面全部文章,因此需要找出文章标签的不同点
查看网页源代码,发现所有文章的信息都保存在 li 标签下面
根据网页结构,输出xpath解析语法,得到20个 li 标签(20个文章)
xpath("//div[@class='new-article']/ul[@class='art-list']//li")
再通过遍历xpath解析的内容,就可以获取该文章的所需信息,每遍历一次 li 标签的内容,获得一篇文章的所有信息
for num, li in enumerate(li_list):
pass
num是用来登记li标签在li_list的索引位置,用来记录是第几篇文章(文件命名和爬虫提示用)
关联Items.py文件,爬取的信息会储存在里面
item = ScrapyTestFileItem()
根据爬虫需求,分别解析文章的链接、标题、作者、发布时间信息
# 文章链接
item['href'] = li.xpath(".//a/@href").extract_first()
# 文章链接是缺漏的,需要拼接成完成的链接
item['href'] = 'https://www.jzda001.com' + item['href']
# 文章标题
item['title'] = li.xpath(".//div[contains(@class,'title')]/p[contains(@class,'line')]//text()").extract()[0]
# 文章作者
item['author'] = li.xpath(".//div[@class='author']/p[@class='name']//text()[1]").extract()
# 文章时间
item['time'] = li.xpath("//div[@class='author']/p[@class='name']//text()[2]").extract()
# 储存遍历位置
item['index_page'] = num
注意:源代码中的文章链接是缺漏的,通过查看文章实际链接对比,发现在文章链接前添加主页面链接即可
若遍历完所有文章内容后再分别保存文件,逻辑容易混乱
最好就是每遍历一个 li 标签,解析其全部信息后,就访问其文章链接,获取并保存文章内容,访问图片链接并保存图片到指定文件夹
再进行下一个 li 标签的访问
因此,需要返回一个yield,该yield的作用是暂定遍历,先执行yield里面的内容(访问文章链接)
yield scrapy.Request(item['href'], callback=self.parse_detail, meta={'item': item})
scrapy.Request scrapy自带访问函数
itme['href'] 里面只有该 li 解析出来的链接
callback 回调参数,跳到指定函数执行
meta 用于函数之间传递信息,这里是将字典item的内容传到函数 parse_detail 中
当访问的文章信息以字典的形式传输到 parse_detail 后,就开始访问文章,并提取其图片链接和文本内容
item = response.meta['item']
新建对象来引用meta中的item
item['text'] = response.xpath("//div[@class='content']/p/text()").extract()
item['img_url'] = response.xpath("//div[@class='pgc-img']/img/@src").extract()
yield item
将信息储存在新建的对象中,并返回至pipelines.py(管道)进行数据处理
步骤四:pipelines.py(数据处理)
此时,item中储存了被访问文章的文章链接、文章标题、文章作者、发布时间、文章位置、文章内容、图片链接,而部分内容需进行二次处理 才能使用,pipelines就是做数据处理和下载保存等操作。
清洗文章标题
item['title'] = item['title'].replace(':', ' ').replace('!', ' ').replace('|', ' ').replace('/', ' ').replace(
' ', ' ').replace(',', ',')
清洗文章作者
author = [i for i in item['author'] if i != '.'][0]
item['author'] = author.replace(' ', '').replace('\n', '')
清洗文章发布时间
time = [i for i in item['time'] if i != '.'][0]
time = time.replace(' ', '').replace('\n', '').replace(':', ' ')
time = time[:10] + ' ' + time[10:]
item['time'] = time
拼接文章内容
item['text'] = [txt + txt for txt in item['text']]
拼接文章地址
file_name = "data"
item['path'] = r'd:/' + file_name + '/' + item['title'] + '/'
待数据清洗完毕后,开始进行信息保存操作
创建文件夹
if not os.path.exists(item['path']):
os.makedirs(item['path'])
文件地址根据文章标题而设立
访问图片链接,并保存到指定文件中
# 访问文章图片链接
for index_img, img_url in enumerate(item['img_url']):
# 文件名称
img_name = item['title'] + ' 图片' + '{}'.format(index_img + 1)
# 文件地址
img_path = item['path'] + img_name + '.png'
# 图片信息
content_img = requests.get(img_url)
with open(img_path, 'wb') as f:
f.write(content_img.content)
print('第{}张图片保存成功,保存地址为:'.format(index_img + 1), img_path)
保存文章内容
# 文本名称
txt_name = str(item['title'] + ' ' + item['author'] + ' ' + item['time'])
# 文本地址
txt_path = item['path'] + '/' + txt_name + '.txt'
with open(txt_path, 'w', encoding='utf-8') as f:
f.write(str(item['text']))
f.close()
再根据爬取内容,设立执行提示和下载访问延迟等操作
完整代码
jzda.py
# !/usr/bin/python3.9
# -*- coding:utf-8 -*-
# @author:inganxu
# CSDN:inganxu.blog.csdn.net
# @Date:2021年12月25日
import scrapy
from ..items import ScrapyTestFileItem
class JzdaSpider(scrapy.Spider):
name = 'jzda'
allowed_domains = ['jzda001.com']
start_urls = ['https://www.jzda001.com/']
# 访问主页面
def parse(self, response):
# 分组
li_list = response.xpath("//div[@class='new-article']/ul[@class='art-list']//li")
print('li标签数:', len(li_list))
for num, li in enumerate(li_list):
item = ScrapyTestFileItem()
item['href'] = li.xpath(".//a/@href").extract_first()
item['href'] = 'https://www.jzda001.com' + item['href']
item['title'] = li.xpath(".//div[contains(@class,'title')]/p[contains(@class,'line')]//text()").extract()[0]
item['author'] = li.xpath(".//div[@class='author']/p[@class='name']//text()[1]").extract()
item['time'] = li.xpath("//div[@class='author']/p[@class='name']//text()[2]").extract()
item['index_page'] = num
yield scrapy.Request(item['href'], callback=self.parse_detail, meta={'item': item})
# 访问文章
def parse_detail(self, response):
item = response.meta['item']
item['text'] = response.xpath("//div[@class='content']/p/text()").extract()
item['img_url'] = response.xpath("//div[@class='pgc-img']/img/@src").extract()
yield item
# spider只做访问处理,下载、保存操作均放在pipelines.py里面
pipelines.py
# !/usr/bin/python3.9
# -*- coding:utf-8 -*-
# @author:inganxu
# CSDN:inganxu.blog.csdn.net
# @Date:2021年12月25日
# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html
# useful for handling different item types with a single interface
import re
import os
import requests
class ScrapyTestFilePipeline:
def process_item(self, item, spider):
# 数据清洗
item = self.precess_clear(item)
print('*' * 10, '第{}篇文章'.format(item['index_page'] + 1), '*' * 10)
print('文章网址:', item['href'])
print('文章标题:', item['title'])
print('文章作者:', item['author'])
print('文章时间:', item['time'])
print('文章地址:', item['path'])
# 创建文件夹
if not os.path.exists(item['path']):
os.makedirs(item['path'])
# 访问文章图片链接
for index_img, img_url in enumerate(item['img_url']):
# 文件名称
img_name = item['title'] + ' 图片' + '{}'.format(index_img + 1)
# 文件地址
img_path = item['path'] + img_name + '.png'
# 图片信息
content_img = requests.get(img_url)
with open(img_path, 'wb') as f:
f.write(content_img.content)
print('第{}张图片保存成功,保存地址为:'.format(index_img + 1), img_path)
# 保存文章文本
# 文本名称
txt_name = str(item['title'] + ' ' + item['author'] + ' ' + item['time'])
# 文本地址
txt_path = item['path'] + '/' + txt_name + '.txt'
with open(txt_path, 'w', encoding='utf-8') as f:
f.write(str(item['text']))
f.close()
print("该页面文本保存成功,文本地址为:", txt_path)
print('\n')
print("第{}篇文章信息爬取成功!!!".format(item['index_page'] + 1))
return item
def precess_clear(self, item):
# 清洗文章标题名字
# item['title'] = [re.sub(r':|!|\||/| |','',i) for i in item['title']]
item['title'] = item['title'].replace(':', ' ').replace('!', ' ').replace('|', ' ').replace('/', ' ').replace(
' ', ' ').replace(',', ',')
# 清洗文章作者名称
author = [i for i in item['author'] if i != '.'][0]
item['author'] = author.replace(' ', '').replace('\n', '')
# 清洗文章时间
time = [i for i in item['time'] if i != '.'][0]
time = time.replace(' ', '').replace('\n', '').replace(':', ' ')
time = time[:10] + ' ' + time[10:]
item['time'] = time
# 拼接文章内容
item['text'] = [txt + txt for txt in item['text']]
# 拼接文章地址
file_name = "jzda_file"
item['path'] = r'd:/' + file_name + '/' + item['title'] + '/'
return item
items.py
# !/usr/bin/python3.9
# -*- coding:utf-8 -*-
# @author:inganxu
# CSDN:inganxu.blog.csdn.net
# @Date:2021年12月25日
# Define here the models for your scraped items
#
# See documentation in:
# https://docs.scrapy.org/en/latest/topics/items.html
import scrapy
class ScrapyTestFileItem(scrapy.Item):
# 文章链接
href = scrapy.Field()
# 文章标题
title = scrapy.Field()
# 文章作者
author = scrapy.Field()
# 发布时间
time = scrapy.Field()
# 图片链接
img_url = scrapy.Field()
# 文章内容
text = scrapy.Field()
# 文件地址
path = scrapy.Field()
# 文章序号
index_page = scrapy.Field()
# 图片序号
index_img = scrapy.Field()
结语
后续有时间再对该案例扩展反爬措施(ip 池、表头池、响应访问)、数据处理(数据分析、数据可视化)、并发措施(并发、分布式)、数据储存(mysql、MongoDB)等内容
可根据自行需求,调整爬虫需求,例如:增加横向排列的文章和示例图;爬取页面侧边的“最新咨询”、“24小时热点”、“精选媒体人”等等..