0
点赞
收藏
分享

微信扫一扫

零基础爬虫入门(六) | 面向百度百科得深度与宽度优先爬虫



  大家好,我是不温卜火,是一名计算机学院大数据专业大三的学生,昵称来源于成语—​​不温不火​​​,本意是​​希望自己性情温和​​​。作为一名互联网行业的小白,博主写博客一方面是为了记录自己的学习过程,另一方面是总结自己所犯的错误希望能够帮助到很多和自己一样处于起步阶段的萌新。但由于水平有限,博客中难免会有一些错误出现,有纰漏之处恳请各位大佬不吝赐教!


目录

  • ​​一、需求分析​​
  • ​​1.1、爬什么​​
  • ​​1、 网站​​
  • ​​1.2、存哪里​​
  • ​​1、本地磁盘文件​​
  • ​​2、数据库​​
  • ​​3、数据仓库​​
  • ​​1.3、怎么爬​​
  • ​​1.4、怎么抽​​
  • ​​1.5、怎么存​​
  • ​​二、详细设计​​
  • ​​2.1、怎么爬​​
  • ​​2.2、怎么抽​​
  • ​​2.3、怎么存​​
  • ​​三、案例代码​​
  • ​​3.1、抽取url​​
  • ​​3.2、获取新的url​​
  • ​​1、深度优先爬取​​
  • ​​2、 广度优先爬取​​
  • ​​3.3 去重​​
  • ​​1、使用set()方法进行URL去重和层数控制​​
  • ​​2、使用bloom_filter去重​​
  • ​​四、example(此部分只是思路,完整代码在3.3的第2部分)​​
  • ​​4.1、可以控制格式的爬虫​​
  • ​​4.2、可以控制层数的深度优先爬虫​​
  • ​​1. 代码​​
  • ​​2. 运行结果​​

零基础爬虫入门(六) | 面向百度百科得深度与宽度优先爬虫_html


一个爬虫程序的开发顺序如下图:
零基础爬虫入门(六) | 面向百度百科得深度与宽度优先爬虫_html_02


一、需求分析

1.1、爬什么

1、 网站

如果我们要爬取网站,在此我们以百度百科为例:

  • 1. 如果我们想要查看网络爬虫词条开始的三层节点,首先我们需要了解网页结构
    零基础爬虫入门(六) | 面向百度百科得深度与宽度优先爬虫_大数据_03
  • 2. 如果我们想要找到词条名称、URL、描述、关键字信息等数据,那么我们需要了解数据存放位置
    零基础爬虫入门(六) | 面向百度百科得深度与宽度优先爬虫_html_04
    零基础爬虫入门(六) | 面向百度百科得深度与宽度优先爬虫_深度优先_05

1.2、存哪里

关于数据存储到哪里?其实学长我的个人感受是要具体情况集体分析,视具体情况而定。因为可选太多了。当然了在确定存储之前,我们还是要先确定存放位置、文件类型的。常见的存储如下:

1、本地磁盘文件

  • 如果只是存储到本地的话,我们常使用的有​​txt​​​、​​csv​​格式等。

2、数据库

  • 1. 关系型数据
  • 1.Mysql
    我们比较常用的是mysql,至于为什么常用大家心知肚明,开源免费。当然了它能存储的数据量与吞吐量有限。
    零基础爬虫入门(六) | 面向百度百科得深度与宽度优先爬虫_python_06
  • 2.SQLServer
  • 3.Oracle
  • 2. 非关系型数据库
  • 1.MongoDB
  • 2.redis
  • 3.hbase

3、数据仓库

  • hive

1.3、怎么爬

网站我们在此选择的为百度百科,至于为什么选择百度百科来作为案例!是因为百度百科比较稳定。

  • 爬取策略
    无更新(百度知识比较稳定) or 深度/广度优先

1.4、怎么抽

  • 数据
    比如说要抽取description、keyword、summary这些数据

  • 方法
    先使用字符串截取,因为现在还没有使用到正则表达式

1.5、怎么存

我们先把文件当成数据的存储载体

二、详细设计

2.1、怎么爬


  • 选一个python库作为主力库
  • 无更新(百度百科较稳定) =>即爬即弃
  • 深度/广度优先=>数据结构设计
  • URL去重=>逻辑设计、方法选择


2.2、怎么抽


  • 字符串截取=>str自带方法
  • 正则表达式=>设计正则表达式
  • Python库抽取=>bs4


2.3、怎么存

文件单存或者批量存法

三、案例代码

3.1、抽取url

引入urllib包和re包后,首先请求对网站进行访问,然后分析网页源代码,并使用正则表达式编写信息抽取规则,使用findall方法将所有正则表达式所匹配的信息抽取出来。

  • 代码如下
import urllib.request as ur
import re
r = ur.urlopen("https://baike.baidu.com/item/%E7%BD%91%E7%BB%9C%E7%88%AC%E8%99%AB") # 对百度百科关键词条为网络爬虫的URL的进行访问
content = r.read().decode('utf-8')
href = re.compile(r'href=[\'"]?(/item[^\'" >]+)') # 利用正则表达式将网页中所需的链接表达出来
new_urls=href.findall(content) # 使用findall方法将所有链接信息抽取出来
print(new_urls) # 打印该网页中的所有链接
  • 运行结果
    零基础爬虫入门(六) | 面向百度百科得深度与宽度优先爬虫_大数据_07
    可能有些同学不知道URL中的乱码是什么意思。
    学长在此再次重申一遍,此部分为URL编码,我们通过解码可以看到其解码后即为我们的关键词网络爬虫。如下图
    零基础爬虫入门(六) | 面向百度百科得深度与宽度优先爬虫_大数据_08
    在线解码工具(点击蓝色字体):​​​站长工具​​

3.2、获取新的url


经历了上一步的爬取后,我们获取了当前网页的所有链接的URL。但是我们是否可以对所爬取的链接再进行进一步的爬取呢?下面我们尝试对网页进行深度优先爬取。
在获得了第一层网页的链接信息后,对URL进行拼接,并不断得对新获取URL进行爬取


1、深度优先爬取

  • 1. 源码
count = 0
r = re.compile(r'href=[\'"]?(/item[^\'" >]+)') # 抽取所需链接信息的正则语言规则
seed = "/item/%E7%BD%91%E7%BB%9C%E7%88%AC%E8%99%AB" # 这是网络爬虫词条
stack = [seed] # 设置种子链接的栈(使用列表模拟栈)
storage = {}
while count < 8:
try:
url = stack.pop(-1) # 取出栈的最后一条URL
html = ur.urlopen("https://baike.baidu.com"+url).read().decode('utf-8') # 对URL进行拼接
new_urls = r.findall(html) # 提取当前网页下的所有链接URL信息
print(new_urls)
stack.extend(new_urls) # 将新提取的链接信息入队列
storage[url] = len(new_urls)
count += 1
except Exception as e:
print(url)
print(e)
  • 2. 运行结果
    零基础爬虫入门(六) | 面向百度百科得深度与宽度优先爬虫_html_09

2、 广度优先爬取

  • 1. 源码
count = 0
r = re.compile(r'href=[\'"]?(/item[^\'" >]+)') # 抽取所需链接信息的正则语言规则
seed = "/item/%E7%BD%91%E7%BB%9C%E7%88%AC%E8%99%AB" # 这是网络爬虫词条
queue = [seed] # 设置种子链接的队列(使用列表模拟队列)
storage = {}
while count < 8:
try:
url = queue.pop(0) # 取出队列最后一条URL
html = ur.urlopen("https://baike.baidu.com"+url).read().decode('utf-8') # 对URL进行拼接
new_urls = r.findall(html) # 提取当前网页下的所有链接URL信息
print(new_urls)
queue.extend(new_urls) # 将新提取的链接信息入队列
storage[url] = len(new_urls)
count += 1
except Exception as e:
print(url)
print(e)
  • 2. 运行结果
    零基础爬虫入门(六) | 面向百度百科得深度与宽度优先爬虫_深度优先_10

3.3 去重

1、使用set()方法进行URL去重和层数控制


从上一个爬虫爬取的结果可以看出,由于网页的最后一个链接都是相同的,所以在第一次爬取后再爬取的所有信息都将是同一个链接,即爬取的是重复信息,而且爬虫将陷入无限循环。
因此我们需要对爬虫进行改进,当网页链接中存在相同链接时,有必要对URL进行去重处理。即我们可以将爬取过的URL存放在一个元素集合中,在进行新的爬取之前将目标URL与爬取过的集合进行对比,只爬取元素集合中没有的URL,就可以完成去重处理了。并且设置限制爬取链接的层数。


  • 1. 源码
#加层数控制的
count = 0 # 层数
floors = 1 # 限制爬取的层数
lastStep = []
r = re.compile(r'href=[\'"]?(/item[^\'" >]+)')
seed = '/item/%E7%BD%91%E7%BB%9C%E7%88%AC%E8%99%AB' # 这是网络爬虫词条
queue = [[seed]]
for i in range(floors): # 限制爬取范围在设定的层数范围内
queue.append([])
storage = {}
used = set() # 设置集合存放爬取过的url
while len(queue[0])>0 or count!=0: # 种子队列不为空或者层数不为零
try:
url = queue[count].pop(-1)
print(url+" "+str(count)) # 打印当前链接和层数
html = ur.urlopen('https://baike.baidu.com'+url).read().decode('utf-8')
storage[url]=html
used.add(url) # 将爬取过的URL放入集合中
new_urls = r.findall(html)
if count < floors:
for new_url in set(new_urls):
if new_url not in used and new_url not in queue:#判断新链接网址中的包含的链接是否为重复的
queue[count+1].append(new_url) # 将爬取的URL存入到队列中相应层数的列表
count+=1
else:
if len(queue[count])==0:
count -= 1
except Exception as e:
print(url)
print(e)
  • 2. 运行结果
    零基础爬虫入门(六) | 面向百度百科得深度与宽度优先爬虫_深度优先_11

2、使用bloom_filter去重


使用​​ScalableBloomFilter​​方法去重,设置了链接的限制爬取层数。并编写了​​getinfo​​函数以对数据进行格式化处理。
并且为了方便数据之后的使用,编写​​saveinfile​​函数,将爬取的数据存入到文件中。


  • 1. 源码
#加bloom filter和文件的
from __future__ import unicode_literals
import urllib.request as ur
import re
from pybloom_live import ScalableBloomFilter
import json
import codecs

def getinfo(html,urls): # 格式化爬取的数据,并将其存入字典中
dict={}
if html.find("<title>") and html.find("</title>"):
dict['title'] = (html[html.find("<title>") + 7:html.find("</title>")])
if html.find("name=\"description\" content=\"") and html.find("..."):
dict['description']=(html[html.find("name=\"description\" content=\"")+28:html.find("...")])
if html.find("name=\"keywords\" content=\"") and html.find("<meta name=\"image\""):
dict['keywords']=(html[html.find("name=\"keywords\" content=\"")+25:html.find("<meta name=\"image\"")-3])
dict['html']=html
dict['childurls']=list(urls)
return dict

def saveinfile(data): # 创建文件存储爬取的数据
fp = codecs.open('.\output.txt', 'a+', 'utf-8') # 初始化创建文件的方法
fp.write(json.dumps(data, ensure_ascii=False)) # 将数据写入文件
fp.close()
#初始化ScalableBloomFilter去重方法
urlbloomfilter=ScalableBloomFilter(initial_capacity=100, error_rate=0.001, mode=ScalableBloomFilter.LARGE_SET_GROWTH)
count = 0 # 层数
floors = 1 # 设置爬取链接的层数限制
lastStep = []
r = re.compile(r'href=[\'"]?(/item[^\'" >]+)')
seed = '/item/%E7%BD%91%E7%BB%9C%E7%88%AC%E8%99%AB' # 这是网络爬虫词条
urlbloomfilter.add(seed)
queue = [[seed]]
for i in range(floors): # 限制爬取范围在设置的层数范围内
queue.append([])
storage = {}
used = set()
while len(queue[0])>0 or count!=0:
try:
url = queue[count].pop(-1)
print(url+" "+str(count))
html = ur.urlopen('https://baike.baidu.com'+url).read().decode('utf-8')
new_urls = r.findall(html)
storage[ur.unquote(url[6:])]=getinfo(html,set(new_urls)) # 使用unquote解码,并调用getinfo函数将格式化后的数据存入字典中
if count < floors:
for new_url in set(new_urls):
if new_url not in urlbloomfilter:
urlbloomfilter.add(new_url) # 将爬取过的URL存入去重过滤器
queue[count+1].append(new_url)
count+=1
else:
if len(queue[count])==0:
count -= 1
except Exception as e:
print(url)
print(e)
saveinfile(storage)
  • 2. 运行结果
    零基础爬虫入门(六) | 面向百度百科得深度与宽度优先爬虫_深度优先_12
    零基础爬虫入门(六) | 面向百度百科得深度与宽度优先爬虫_数据_13

四、example(此部分只是思路,完整代码在3.3的第2部分)

4.1、可以控制格式的爬虫


初始化实现层数控制:实际上在到达控制层数之前都是执行深度优先,在控制层需要宽度优先


# 传统的不限制层数的操作
#转换成utf-8,网页不会被转码,可以直接观看
import urllib.request as ur
import re


#初始化参数
count = 3 # 控制层数
# 初始化数据存储结构
r = re.compile(r'href=[\'"]?(/item[^\'" >]+)')
seed = "/item/%E7%BD%91%E7%BB%9C%E7%88%AC%E8%99%AB" # 这是网络爬虫词条
queue = [seed] # 设置种子链接的队列(使用列表模拟队列)
storage = {}
# 初始化实现层数控制:实际上在到达控制层数之前都是执行深度优先,在控制层需要宽度优先
while len(queue) > 0:
try:
url = queue.pop(-1) # 取出队列最后一条URL
html = ur.urlopen("https://baike.baidu.com" + url).read().decode('utf-8') # 对URL进行拼接
print(html)
new_urls = r.findall(html) # 提取当前网页下的所有链接URL信息
queue.extend(new_urls)
storage[url] = len(new_urls)
except Exception as e:
print(url)
print(e)

零基础爬虫入门(六) | 面向百度百科得深度与宽度优先爬虫_数据_14

4.2、可以控制层数的深度优先爬虫

1. 代码

import urllib.request as ur
import re

#初始化参数
count = 0 # 控制层数
floors = 2 # 在程序开始时可以设定需要爬取的层数
# 初始化数据存储结构
r = re.compile(r'href=[\'"]?(/item[^\'" >]+)')
seed = "/item/%E7%BD%91%E7%BB%9C%E7%88%AC%E8%99%AB" # 这是网络爬虫词条
queue = [[seed]] # 设置种子链接的队列(使用列表模拟队列)
# 初始化实现层数控制:实际上在到达控制层数之前都是执行深度优先,在控制层需要宽度优先
# 嵌套list在不去改变爬虫的深度优先的策略基础上,保证队列处于一个堆栈的状态
for i in range(floors):
queue.append([]) # 使用嵌套list存放不同层的数据
storage = {} # 字典型,用于保存数据
used = set() # 集合,自带去重的特性
while len(queue[0])>0 or count!=0: # 所有层数爬取完毕+列表中没有url时结束
try:
# 取出队列最后一条URL
url = queue[count].pop(-1)
print(url+" "+str(count))
html = ur.urlopen('https://baike.baidu.com'+url).read().decode('utf-8') # 对URL进行拼接
storage[url] = html
used.add(url)
new_urls = r.findall(html) # 提取当前网页下的所有链接URL信息
# 在没有达到控制层前,页面引入的URL都会被加入到该层的list中,完成深度优先
if count < floors:
for new_url in set(new_urls):
if new_url not in used and new_url not in queue:
queue[count+1].append(new_url)
count+=1
# 到达控制层,不再增加url,直接通过list.pop完成开暗渡优先
else:
if len(queue[count]) == 0:
count -= 1
except Exception as e:
print(url)
print(e)

2. 运行结果

  • 1.我们先打上断点进行测试
    零基础爬虫入门(六) | 面向百度百科得深度与宽度优先爬虫_大数据_15
  • 2.开始测试
    零基础爬虫入门(六) | 面向百度百科得深度与宽度优先爬虫_大数据_16
    我们发现它在循环的进行爬取。大概到70左右进入queue[2]
    零基础爬虫入门(六) | 面向百度百科得深度与宽度优先爬虫_html_17
  • 3. 下面我们看下运行结果
    零基础爬虫入门(六) | 面向百度百科得深度与宽度优先爬虫_html_18
    零基础爬虫入门(六) | 面向百度百科得深度与宽度优先爬虫_python_19

美好的日子总是短暂的,虽然还想继续与大家畅谈,但是本篇博文到此已经结束了,如果还嫌不够过瘾,不用担心,我们下篇见!

零基础爬虫入门(六) | 面向百度百科得深度与宽度优先爬虫_数据_20


  好书不厌读百回,熟读课思子自知。而我想要成为全场最靓的仔,就必须坚持通过学习来获取更多知识,用知识改变命运,用博客见证成长,用行动证明我在努力。
  如果我的博客对你有帮助、如果你喜欢我的博客内容,请​​“点赞” “评论”“收藏”​​一键三连哦!听说点赞的人运气不会太差,每一天都会元气满满呦!如果实在要白嫖的话,那祝你开心每一天,欢迎常来我博客看看。
  码字不易,大家的支持就是我坚持下去的动力。点赞后不要忘了​​关注​​我哦!



举报

相关推荐

0 条评论