网页抓取数据百度百科(【干货】如何选择合适的方式爬取数据?(一))
优采云 发布时间: 2021-10-21 12:13网页抓取数据百度百科(【干货】如何选择合适的方式爬取数据?(一))
大家好,这是我第一次写这样的分享项目文章。可能很水,不完整,肯定有一些错误。希望大家在评论中指点迷津。谢谢!
一、前言
网络爬虫(又称网络蜘蛛、网络机器人)是按照一定的规则自动抓取万维网上信息的程序或脚本。其他不太常用的名称是蚂蚁、自动索引、模拟器或蠕虫。------百度百科
用人类的话说,爬虫是用来定时获取海量数据,然后进行处理和使用的。是大数据、金融、机器学习等领域的必要支撑条件之一。
目前在一线城市,爬虫的薪资待遇都比较客观。后来晋升中高级爬虫工程师、数据分析师、大数据开发岗位都是不错的过渡。
二、项目目标
其实这里介绍的项目不需要太复杂。最终目的是将帖子的每条评论爬取到数据库中,并进行数据更新、防止重复爬取、反爬取等措施。
三、项目准备
这部分主要介绍本文用到的工具、涉及的库、网页等信息。
软件:PyCharm
所需的库:Scrapy、selenium、pymongo、user_agent、datetime
目标 网站:
http://bbs.foodmate.net
插件:chromedriver(版本必须正确)
四、项目分析1、确定爬取的结构网站
简而言之:确定网站的加载方式,如何正确进入post逐级抓取数据,使用什么格式保存数据等。
其次,观察网站的层次结构,也就是如何按照版块一点一点的进入post页面。这对于这个爬虫任务非常重要,也是编写代码的主要部分。
2、如何选择正确的方式抓取数据?
目前我知道的爬虫方法大概有以下几种(不全,但比较常用):
1)请求框架:使用这个http库灵活抓取需要的数据。简单但过程有点繁琐,可以配合抓包工具来获取数据。但是需要确定headers和对应的请求参数,否则无法获取数据;很多app爬取,图片视频爬取,爬取停止,比较轻巧灵活,高并发分布式部署也很灵活,功能可以更好的实现。
2)Scrapy框架:scrapy框架可以说是爬虫最常用、最好的爬虫框架。它有很多优点:scrapy 是异步的;它采用更具可读性的 xpath 而不是常规的;强大的统计和日志系统;同时抓取不同的 URL;支持shell模式,方便独立调试;支持编写中间件,方便编写一些统一的过滤器;它可以通过管道存储在数据库中,等等。这也是本次要介绍的框架(结合selenium库)文章。
五、项目实现
1、第一步:确定网站的类型
先说明什么意思,看什么网站,先看网站的加载方式,是静态加载,动态加载(js加载),还是其他方式;根据不同的加载方式需要不同的方法。然后我们观察了今天爬取的网站,发现这是一个按时间顺序排列的论坛。首先猜测是静态加载网站;我们打开了组织js加载的插件,如下图。
刷新后发现确实是静态的网站(如果能正常加载,基本就是静态加载了)。
2、步骤二:确定层级关系
其次,今天我们要爬取的网站是美食论坛网站,它是静态加载的网站,在前面的分析中我们已经了解了,然后是层次结构:
大概就是上面这个过程,一共三个层次的渐进访问,然后到达post页面,如下图。
很多人学习python,不知道从哪里开始。
很多人学习python,掌握了基本语法后,不知道从哪里找案例上手。
许多做过案例研究的人不知道如何学习更高级的知识。
所以对于这三类人,我会为大家提供一个很好的学习平台,免费领取视频教程、电子书、课程源码!
QQ群:721195303
部分代码显示:
一级接口:
def parse(self, response):
self.logger.info("已进入网页!")
self.logger.info("正在获取版块列表!")
column_path_list = response.css('#ct > div.mn > div:nth-child(2) > div')[:-1]
for column_path in column_path_list:
col_paths = column_path.css('div > table > tbody > tr > td > div > a').xpath('@href').extract()
for path in col_paths:
block_url = response.urljoin(path)
yield scrapy.Request(
url=block_url,
callback=self.get_next_path,
)
次要接口:
def get_next_path(self, response):
self.logger.info("已进入版块!")
self.logger.info("正在获取文章列表!")
if response.url == 'http://www.foodmate.net/know/':
pass
else:
try:
nums = response.css('#fd_page_bottom > div > label > span::text').extract_first().split(' ')[-2]
except:
nums = 1
for num in range(1, int(nums) + 1):
tbody_list = response.css('#threadlisttableid > tbody')
for tbody in tbody_list:
if 'normalthread' in str(tbody):
item = LunTanItem()
item['article_url'] = response.urljoin(
tbody.css('* > tr > th > a.s.xst').xpath('@href').extract_first())
item['type'] = response.css(
'#ct > div > div.bm.bml.pbn > div.bm_h.cl > h1 > a::text').extract_first()
item['title'] = tbody.css('* > tr > th > a.s.xst::text').extract_first()
item['spider_type'] = "论坛"
item['source'] = "食品论坛"
if item['article_url'] != 'http://bbs.foodmate.net/':
yield scrapy.Request(
url=item['article_url'],
callback=self.get_data,
meta={'item': item, 'content_info': []}
)
try:
callback_url = response.css('#fd_page_bottom > div > a.nxt').xpath('@href').extract_first()
callback_url = response.urljoin(callback_url)
yield scrapy.Request(
url=callback_url,
callback=self.get_next_path,
)
except IndexError:
pass
三级接口:
def get_data(self, response):
self.logger.info("正在爬取论坛数据!")
item = response.meta['item']
content_list = []
divs = response.xpath('//*[@id="postlist"]/div')
user_name = response.css('div > div.pi > div:nth-child(1) > a::text').extract()
publish_time = response.css('div.authi > em::text').extract()
floor = divs.css('* strong> a> em::text').extract()
s_id = divs.xpath('@id').extract()
for i in range(len(divs) - 1):
content = ''
try:
strong = response.css('#postmessage_' + s_id[i].split('_')[-1] + '').xpath('string(.)').extract()
for s in strong:
content += s.split(';')[-1].lstrip('\r\n')
datas = dict(content=content, # 内容
reply_id=0, # 回复的楼层,默认0
user_name=user_name[i], # ⽤户名
publish_time=publish_time[i].split('于 ')[-1], # %Y-%m-%d %H:%M:%S'
id='#' + floor[i], # 楼层
)
content_list.append(datas)
except IndexError:
pass
item['content_info'] = response.meta['content_info']
item['scrawl_time'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
item['content_info'] += content_list
data_url = response.css('#ct > div.pgbtn > a').xpath('@href').extract_first()
if data_url != None:
data_url = response.urljoin(data_url)
yield scrapy.Request(
url=data_url,
callback=self.get_data,
meta={'item': item, 'content_info': item['content_info']}
)
else:
item['scrawl_time'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
self.logger.info("正在存储!")
print('储存成功')
yield item
3、第三步:确定爬取方式
由于是静态网页,我首先决定使用scrapy框架直接获取数据,通过初步测试,发现该方法确实可行。爬虫限制了爬取速度,导致我被网站限制,网站从静态加载的网页改成:进入网页前动态加载的网页验证算法,直接访问会被拒绝背景。
但是这种问题怎么会是我的小聪明呢?经过短暂的思考(1天),我将方案改为scrapy框架+selenium库的方法,通过调用chromedriver,模拟访问网站等网站加载后,爬取没有完成。后续证明,该方法确实可行且有效。
代码的实现部分如下:
def process_request(self, request, spider):
chrome_options = Options()
chrome_options.add_argument('--headless') # 使用无头谷歌浏览器模式
chrome_options.add_argument('--disable-gpu')
chrome_options.add_argument('--no-sandbox')
# 指定谷歌浏览器路径
self.driver = webdriver.Chrome(chrome_options=chrome_options,
executable_path='E:/pycharm/workspace/爬虫/scrapy/chromedriver')
if request.url != 'http://bbs.foodmate.net/':
self.driver.get(request.url)
html = self.driver.page_source
time.sleep(1)
self.driver.quit()
return scrapy.http.HtmlResponse(url=request.url, body=html.encode('utf-8'), encoding='utf-8',
request=request)
4、第四步:确定爬取数据的存储格式
这部分不用说了,根据自己的需要,在items.py中设置需要爬取的数据格式。只需使用此格式保存在项目中:
class LunTanItem(scrapy.Item):
"""
论坛字段
"""
title = Field() # str: 字符类型 | 论坛标题
content_info = Field() # str: list类型 | 类型list: [LunTanContentInfoItem1, LunTanContentInfoItem2]
article_url = Field() # str: url | 文章链接
scrawl_time = Field() # str: 时间格式 参照如下格式 2019-08-01 10:20:00 | 数据爬取时间
source = Field() # str: 字符类型 | 论坛名称 eg: 未名BBS, 水木社区, 天涯论坛
type = Field() # str: 字符类型 | 板块类型 eg: '财经', '体育', '社会'
spider_type = Field() # str: forum | 只能写 'forum'
5、第五步:确认保存数据库
本项目选用的数据库是 mongodb。因为是非关系型数据库,优势很明显,格式要求也没有那么高。可灵活存储多维数据。一般是爬虫首选的数据库(别跟我说redis,知道的我会用,主要是不会)
代码:
import pymongo
class FMPipeline():
def __init__(self):
super(FMPipeline, self).__init__()
# client = pymongo.MongoClient('139.217.92.75')
client = pymongo.MongoClient('localhost')
db = client.scrapy_FM
self.collection = db.FM
def process_item(self, item, spider):
query = {
'article_url': item['article_url']
}
self.collection.update_one(query, {"$set": dict(item)}, upsert=True)
return item
这时候,有聪明的朋友会问:同一个数据爬两次怎么办?(换句话说,就是重复检查功能)
我之前没想过这个问题。后来问了大佬才知道。这是我们保存数据的时候做的,就这句话:
query = {
'article_url': item['article_url']
}
self.collection.update_one(query, {"$set": dict(item)}, upsert=True)
通过帖子的链接判断是否有重复数据爬取。如果重复数据可以理解为覆盖它,那么数据也可以被更新。
6、其他设置
多线程、头、管道传输顺序等问题,都在settings.py文件中设置。详情请参考编辑器的项目查看。我不会在这里详细介绍。
七、效果展示
1、 点击运行,控制台会显示结果,如下图所示。
2、 中途将很多帖子的爬取任务堆到队列中,再进行多线程处理。我设置了16个线程,速度还是很可观的。
3、数据库显示:
content_info 存储了每个帖子的所有评论以及相关用户的公开信息。
八、总结
1、本文文章主要介绍食物网站数据采集和存储过程,并详细讲解如何分析网页结构、爬取策略和网站类型。,层级关系,爬取方式和数据存储过程,最后实现将帖子的每条评论都爬进数据库,并且可以更新数据,防止重复爬取,反爬取等,干货满满.
2、 总的来说,这个项目并不是特别难。只要思路正确,找到数据规律,可以说是轻而易举。我想只是我之前没有完全走完这个过程。我希望它会对你有所帮助。这将是我最大的荣幸。
3、遇到问题,首先想到的不是问同事、朋友、老师,而是去谷歌、百度,看看有没有类似的情况,看看别人的经历,而你一定要学会自己发现问题、思考问题、解决问题。,这对以后的工作很有帮助(之前有人说我还没有离开我的学生时代,就是我喜欢问同事)。在网上查了一些资料,还是没有头绪,再问别人,别人会做的我更愿意帮你~
还是要推荐一下自己搭建的Python学习群:721195303,群里都是学Python的。如果您想学习或正在学习Python,欢迎您加入。大家是软件开发党,不定期分享干货(仅限Python软件开发相关),包括2021年自己编译的最新Python进阶资料和零基础教学。欢迎进阶有兴趣的朋友在 Python 中加入!