首页 > temp > python入门教程 >
-
BeautifulSoup爬取网页分页
在前面我们介绍了如何通过某个页面爬取与之关联的外部网页,当时介绍的是使用广度优先搜索的方式爬取。
在本节,我们将介绍另一种爬取外部链接的方式,即深度优先搜索,爬取网页的分页。
由于本人喜欢古诗词,今天爬取的网页的内容就是古诗词,爬取的链接为:https://so.gushiwen.org/shiwen/。
如下图所示:
在同一个网页,内容是通过分页的形式进行展示,今天介绍如何爬取分页。
一、思路分析
我们知道,对于网页的分页内容访问,我们通常可以通过点击“上一页”或者“下一页”按钮访问关联的页面。
因此,在爬取分页内容的时候,我们可以采用这一思路,进行深度递归,即可访问所有的分页内容。
当然,事实上,由于软件工程师在编码过程中会对不同分页的url做一定的映射,因此除了以上思路,我们可以选择另外一种解决办法就是分析这种映射关系是什么。
如果能确定映射关系,那么就不需要进行深度递归,毕竟操作系统对于编程语言的栈的深度是有限制的,比如python最多递归1000次,超过1000次将报栈溢出的错误。
在接下来的示例演示中将对两种方法进行介绍。
二、示例演示
1.深度递归访问分页内容
正如之前一直强调的,在使用网络爬虫进行网页爬取的时候,第一步总是先打开目标网页,然后在开发者模式下分析网页的特点,然后根据分析的结果进行爬取。
打开目标网页的结构如下图所示:
在网页中搜索“下一页”按钮所在的标签位置,我们可以看到如上的结果。并且通过认真观察,我们能够看见“下一页”所在的标签是一个超链接,因此我们可以猜测,这个链接就是用来从当前页跳转到下一页。
为了验证我们的猜想是否正确,我们可以直接点开“下一页”按钮的跳转页面是否与我们刚才观察的url一致。
打开“下一页”的页面,我们可以看到url如下:
显然,两个页面是存在联系的。浏览器中的url和超链接的url除了隔了域名https://so.gushiwen.org/不一样之外,后半部分都是一致的。
因此证实了我们的猜想是正确的,因此我们只需要获取页面中“下一页”中超链接的url,再拼接上域名,就能得到下一页页面的url。通过递归操作,我们能够爬取所有的页面。
这里定义一个函数进行实现,使用一个队列存储每次获取的url:
def getNextUrl(url,que):
'''
:return:
'''
if url==None or len(url)==0:
return
try:
html = requests.get(url)
html.encoding = None
new_url = bs.findAll("a", {"class": "amore"})[0].attrs['href']
new_url= domain_prefix+new_url
que.put(new_url)
getNextUrl(new_url,que)
except Exception as e:
print("get attr error!")
在找到不同分页页面间的关联关系之后,接下来就是找到我们的目标内容:古诗词所在的位置。通过我们前面介绍的知识,我们很容易找到我们的目标内容:
利用我们上一章的讲过的标签定位方法,很容易就可以获取到目标内容。
为了方便查看,我们把每首诗的内容使用文件进行存储,代码实现如下:
def get_poetry(bs):
'''
获取每首诗的内容
:param url:
:return:
'''
# 将所有诗词内容写入文本
poetry_set = bs.findAll("div", {"class": "sons"})
for poetry in poetry_set:
titles = poetry.findAll('b')
if len(titles)==0:
break
title=titles[0].text.split('/')[-1].strip()
res=''+title
with open('./book/'+title+".txt", 'w') as fo:
info = poetry.findAll('p', {'class': 'source'})
for i in info:
fo.write(i.get_text())
res+=','+i.get_text()
content = poetry.findAll('div', {'class': 'contson'})
for i in content:
fo.write(i.get_text())
res += ','+i.get_text()
为了提高执行效率,我们可以把页面内容提取和url访问分开,使用多线程进行并行操作。同时,要考虑到访问过程中会存在访问出错的情况,因此需要捕获异常。
最终得到的完整代码如下:
# 请求库
import requests
# 解析库
from bs4 import BeautifulSoup
from queue import Queue
import threading
import os
import re
domain_prefix=r"https://so.gushiwen.org"
def get_poetry(bs):
'''
获取每首诗的内容
:param url:
:return:
'''
# 将所有诗词内容写入文本
poetry_set = bs.findAll("div", {"class": "sons"})
for poetry in poetry_set:
titles = poetry.findAll('b')
if len(titles)==0:
break
title=titles[0].text.split('/')[-1].strip()
res=''+title
with open('./book/'+title+".txt", 'w') as fo:
info = poetry.findAll('p', {'class': 'source'})
for i in info:
fo.write(i.get_text())
res+=','+i.get_text()
content = poetry.findAll('div', {'class': 'contson'})
for i in content:
fo.write(i.get_text())
res += ','+i.get_text()
def getNextUrl(url,que):
'''
:return:
'''
if url==None or len(url)==0:
return
try:
html = requests.get(url)
html.encoding = None
bs = BeautifulSoup(html.text, 'html.parser')
#启动一个子线程进行内容处理
t = threading.Thread(target=get_poetry, args=(bs,))
t.start()
t.join()
new_url = bs.findAll("a", {"class": "amore"})[0].attrs['href']
new_url= domain_prefix+new_url
que.put(new_url)
getNextUrl(new_url,que)
except Exception as e:
print("get attr error!")
爬取的网页链接
url=r"https://so.gushiwen.org/shiwen/"
html=requests.get(url)
html.encoding=None
bs=BeautifulSoup(html.text,'html.parser')
#爬取所有的网页链接
urls=Queue()
getNextUrl(url,urls)
最终得到的结果如下:
2.研究映射关系快速访问
通过递归的方式获取所有的url思路很清晰,也很简单,但是效率不高。因此,为了提高效率,我们可以比较分析不同的特点,找出规律。
摘取部分url展示如下:
通过细细比较,我们可以很容易发现,这些url除了最后的1位或者两位数字不同之外,其他的都是相同的,并且这些数字存在一个以1为步长的线性增长关系。
因此我们完全可以通过一个循环来生成所有的url,代码实现如下:
#所有的网页链接
urls=Queue()
for i in range(1,101,1):
tmp=url.replace("1.aspx",str(i)+".aspx")
print(tmp)
urls.put(tmp)
在得到所有的URL之后,接下来我们只需要抓取每个页面的url的HTML页面,然后获取内容皆可。除了获取url的方式不同之外,其他抓取的方式与深度递归方式差不多,如下所示:
def get_poetry(que):
'''
获取每首诗的内容
:param url:
:return:
'''
while not que.empty():
url=que.get()
try:
html = requests.get(url)
html.encoding = None
bs = BeautifulSoup(html.text, 'html.parser')
# 将所有诗词内容写入文本
poetry_set = bs.findAll("div", {"class": "sons"})
for poetry in poetry_set:
titles = poetry.findAll('b')
if len(titles)==0:
break
title=titles[0].text.split('/')[-1].strip()
print(title)
res=''+title
with open('./book/'+title+".txt", 'w') as fo:
info = poetry.findAll('p', {'class': 'source'})
for i in info:
fo.write(i.get_text())
res+=','+i.get_text()
content = poetry.findAll('div', {'class': 'contson'})
for i in content:
fo.write(i.get_text())
res += ','+i.get_text()
except Exception as e:
print(url)
print("get attr error!")
因为抓取的页面之间不存在先后关系,因此可以并行执行。这里采用多线程的方式来提高抓取的效率。
得到最终的代码如下:
# 请求库
import requests
# 解析库
from bs4 import BeautifulSoup
from queue import Queue
import threading
import pyttsx3
import os
import re
domain_prefix=r"https://so.gushiwen.org"
def get_poetry(que):
'''
获取每首诗的内容
:param url:
:return:
'''
while not que.empty():
url=que.get()
try:
html = requests.get(url)
html.encoding = None
bs = BeautifulSoup(html.text, 'html.parser')
# 将所有诗词内容写入文本
poetry_set = bs.findAll("div", {"class": "sons"})
for poetry in poetry_set:
titles = poetry.findAll('b')
if len(titles)==0:
break
title=titles[0].text.split('/')[-1].strip()
print(title)
res=''+title
with open('./book/'+title+".txt", 'w') as fo:
info = poetry.findAll('p', {'class': 'source'})
for i in info:
fo.write(i.get_text())
res+=','+i.get_text()
content = poetry.findAll('div', {'class': 'contson'})
for i in content:
fo.write(i.get_text())
res += ','+i.get_text()
except Exception as e:
print(url)
print("get attr error!")
# 爬取的网页链接
url=r"https://so.gushiwen.org/shiwen/default_2A515ea88d1858A1.aspx"
#爬取所有的网页链接
urls=Queue()
# urls.put(url)
for i in range(1,101,1):
tmp=url.replace("1.aspx",str(i)+".aspx")
print(tmp)
urls.put(tmp)
# 定义多线程
for i in range(10):
t = threading.Thread(target=get_poetry, args=(urls,))
t.start()
t.join()
通过执行对比分析,我们很容易就可以发现第二种方法比第一种方法不仅代码更为简洁,效率也更高。
当然,抓取内容只是整个数据分析的基本操作,为了对数据进行更深入的理解,我们需要借助一些数据分析的工具对数据进行处理和分析。
由于涉及的内容较多,难度也更大,将在后续的篇章逐渐给大家进行介绍。
出处: