网站自动采集文章(【干货】如何选择合适的方式爬取数据(一) )

优采云 发布时间: 2022-01-01 01:12

  网站自动采集文章(【干货】如何选择合适的方式爬取数据(一)

)

  一、前言

  是的,Ao,火辣的蓝哥是四个我~

  

  网络爬虫(又称网络蜘蛛、网络机器人)是按照一定的规则自动抓取万维网上信息的程序或脚本。其他不太常用的名称包括蚂蚁、自动索引、模拟器或蠕虫。 ------百度百科

  说到人,爬虫是用来定期获取海量数据,然后进行处理和使用的。是大数据、金融、机器学习等必要的支撑条件之一。

  目前一线城市,爬虫的薪资待遇都比较客观。后来晋升中高级爬虫工程师、数据分析师、大数据开发岗位等都是很好的过渡。

  二、项目目标

  这次介绍的项目不需要太复杂。最终目的是将帖子的每条评论爬取到数据库中,并更新数据,防止重复爬取、反爬取等措施。

  三、项目准备

  这部分主要介绍本文用到的工具、涉及的库、网页等信息。

  软件:PyCharm

  所需库:Scrapy、selenium、pymongo、user_agent、datetime

  目标网站:

  http://bbs.foodmate.net

  插件:chromedriver(版本必须正确)

  四、项目分析1、确定爬取的结构网站

  简而言之:确定网站的加载方式,如何正确进入帖子逐级抓取数据,使用什么格式保存数据等

  其次观察网站的层级结构,也就是如何按照版块一点一点的进入post页面。这对于这个爬虫任务来说非常重要,也是编写代码的主要部分。

  2、如何选择正确的数据抓取方式?

  目前我知道的爬取方法如下(不全,但比较常用):

  1)请求框架:这个http库可以灵活的抓取需要的数据,简单但是过程有点繁琐,可以配合抓包工具来获取数据。但是需要确定headers和对应的请求参数,否则无法获取数据;很多app爬取,图片视频爬取,爬取停止,比较轻量灵活,高并发分布式部署也很灵活,功能可以更好的实现。

  2)Scrapy框架:scrapy框架可以说是最常用的爬虫,也是最好用的爬虫框架。它有很多优点:scrapy 是异步的;采用更具可读性的 xpath 而不是常规的;强大的统计和日志系统;同时抓取不同的网址;支持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,我也会用,主要不会)

  代码:

  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、点击Run,控制台会显示结果,如下图。

  

  

  2、 中间会有很多帖子在队列中爬取任务,然后多线程处理,我设置了16个线程,速度还是很可观的。

  

  3、数据库展示:

  

  Content_info 存储了每个帖子的所有评论以及相关用户的公开信息。

  八、总结

  1、本文文章主要向大家介绍食物网站的数据采集和存储过程,并详细讲解如何分析网页结构、爬取策略,网站类型、层级关系、抓取方式和数据存储程序,最终实现将帖子的每条评论抓取到数据库中,并更新数据防止重复抓取、反抓取等,干货已满。

  2、 总的来说,这个项目并不是特别难。只要思路对了,数据规律找到了,可以说是轻而易举。这次介绍的水,希望能对大家有所帮助,是我最大的荣幸。

  3、遇到问题,首先想到的不是问同事、朋友、老师,而是去谷歌、百度,看看有没有类似的情况,看看别人的经验,你必须学会​​自己发现和思考问题。 , 解决问题,这对以后的工作会有很大的帮助(之前有人说我没有离开我的学生时代,就是我喜欢问同事)。我在网上查了一些资料,还是没有头绪,再问别人。别人会更愿意帮你~

  最后和大家分享我的座右铭:独立思考,不卑不亢。

  

  结束

  最后因为平台不支持外链

  需要本文项目代码的小伙伴,请私信我的“代码”领取

  需要更多资料和课件的小伙伴,请私信我的“资源”接收

  如果您在操作过程中遇到任何问题,也可以在评论区讨论!

  

  

  

  ------------------- 结束-------------------

  

0 个评论

要回复文章请先登录注册


官方客服QQ群

微信人工客服

QQ人工客服


线