网页抓取数据百度百科(网络爬虫(蜘蛛)如何选择合适的方式有哪些?)
优采云 发布时间: 2021-12-13 17:21网页抓取数据百度百科(网络爬虫(蜘蛛)如何选择合适的方式有哪些?)
一、前言
网络爬虫(也称为网络蜘蛛或网络机器人)是一种按照一定的规则自动抓取万维网上信息的程序或脚本。其他不太常用的名称包括蚂蚁、自动索引、模拟器或蠕虫。------百度百科
用人类的话说,爬虫是用来定时获取海量数据,然后进行处理和使用的。是大数据、金融、机器学习等领域的必要支撑条件之一。
目前一线城市,爬虫的薪资待遇都比较客观,以后晋升中高级爬虫工程师、数据分析师、大数据开发岗位都是不错的过渡。
二、项目目标
其实这里介绍的项目不需要太复杂。最终目的是将帖子的每条评论爬取到数据库中,并进行数据更新、防止重复爬取、反爬取等措施。
三、项目准备
这部分主要介绍本文用到的工具、涉及的库、网页等信息等。
软件:PyCharm
所需的库:Scrapy、selenium、pymongo、user_agent、datetime
目标 网站:
美食论坛
插件:chromedriver(版本必须正确)
四、项目分析1、 确定网站的结构
简而言之:确定网站的加载方式,如何正确进入post逐层抓取数据,使用什么格式保存数据等。
其次,观察网站的层次结构,也就是如何按照版块一点一点的进入post页面。这对于这个爬虫任务来说非常重要,也是编写代码的主要部分。
2、如何选择合适的方法来捕获数据?
目前我知道的爬取方法如下(不全,但比较常用):
请求框架:利用这个http库灵活抓取需要的数据。简单但过程有点繁琐,可以配合抓包工具来获取数据。但是需要确定headers和对应的请求参数,否则无法获取数据;很多app爬取,图片视频爬取,爬取停止,比较轻量灵活,高并发分布式部署也很灵活,功能可以更好的实现。Scrapy框架:scrapy框架可以说是爬虫最常用、最好的爬虫框架。它有很多优点:scrapy 是异步的;它采用更具可读性的 xpath 而不是常规的;强大的统计和日志系统;同时在不同的 url 上爬行;支持shell模式,方便独立调试;支持编写中间件,方便编写一些统一的过滤器;它可以通过管道存储在数据库中,等等。这也是本次要介绍的框架(结合selenium库)文章。五、项目实现1、 第一步:确定网站的类型
先说明什么意思,看什么网站,先看网站的加载方式,是静态加载,动态加载(js加载),还是其他方式;根据不同的加载方式需要不同的方法。然后我们观察了今天爬取的网站,发现这是一个按时间顺序排列的论坛。首先猜测是静态加载网站;我们开启了组织js加载的插件,如下图。
刷新后发现确实是静态的网站(如果能正常加载,基本就是静态加载了)。
2、第二步:确定层级关系
其次,今天我们要爬取的网站是美食论坛网站,它是静态加载的网站,在前面的分析中我们已经了解了,然后是层次结构:
大概就是上面这个过程,一共三个层次的渐进访问,然后到达post页面,如下图。
部分代码显示:
一级接口:
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,知道的我就用,主要是不会)
piplines.py 代码的一部分:
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 存储了每个帖子的所有评论以及相关用户的公开信息。
欢迎大家点赞、留言、转发、转载,感谢您的陪伴与支持