网页抓取数据百度百科(网络爬虫(蜘蛛)如何选择合适的方式有哪些?)

优采云 发布时间: 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 存储了每个帖子的所有评论以及相关用户的公开信息。

  欢迎大家点赞、留言、转发、转载,感谢您的陪伴与支持

0 个评论

要回复文章请先登录注册


官方客服QQ群

微信人工客服

QQ人工客服


线