023 python3 BeautifulSoup模块使用与Python爬虫爬取博客园作业

BeautifulSoup就是Python的一个HTML或XML的解析库,可以用它来方便地从网页中提取数据。官方解释如下:

Beautiful Soup提供一些简单的、Python式的函数来处理导航、搜索、修改分析树等功能。它是一个工具箱,通过解析文档为用户提供需要抓取的数据,因为简单,所以不需要多少代码就可以写出一个完整的应用程序。

Beautiful Soup自动将输入文档转换为Unicode编码,输出文档转换为UTF-8编码。你不需要考虑编码方式,除非文档没有指定一个编码方式,这时你仅仅需要说明一下原始编码方式就可以了。

Beautiful Soup已成为和lxml、html6lib一样出色的Python解释器,为用户灵活地提供不同的解析策略或强劲的速度。

所以说,利用它可以省去很多烦琐的提取工作,提高了解析效率。

一、模块安装

使用之前,需要确保已经安装好了BeautifulSoup和lxml模块。

pip install beautifulsoup4 lxml

二、解析器选择

Beautiful Soup在解析时实际上依赖解析器,它除了支持Python标准库中的HTML解析器外,还支持一些第三方解析器(比如lxml)。下表为BeautifulSoup支持的解析器:

解析器

使用方法

优势

劣势

Python标准库

BeautifulSoup(markup, "html.parser")

Python的内置标准库、执行速度适中、文档容错能力强

Python 2.7.3及Python 3.2.2之前的版本文档容错能力差

lxml HTML解析器

BeautifulSoup(markup, "lxml")

速度快、文档容错能力强

需要安装C语言库

lxml XML解析器

BeautifulSoup(markup, "xml")

速度快、唯一支持XML的解析器

需要安装C语言库

html5lib

BeautifulSoup(markup, "html5lib")

最好的容错性、以浏览器的方式解析文档、生成HTML5格式的文档

速度慢、不依赖外部扩展

通过以上对比可以看出,lxml解析器有解析HTML和XML的功能,而且速度快,容错能力强,所以推荐使用它。

如果使用lxml,那么在初始化Beautiful Soup时,可以把第二个参数改为lxml即可:

from bs4 import BeautifulSoup
soup = BeautifulSoup('<p>Hello</p>', 'lxml')
print(soup.p.string)

三、基本用法

from bs4 import BeautifulSoup
import requests, re

req_obj = requests.get('https://www.baidu.com')
soup = BeautifulSoup(req_obj.text, 'lxml')

'''标签查找'''
print(soup.title)  # 只是查找出第一个
print(soup.find('title'))  # 效果和上面一样
print(soup.find_all('div'))  # 查出所有的div标签

'''获取标签里的属性'''
tag = soup.div
print(tag['class'])  # 多属性的话,会返回一个列表
print(tag['id'])  # 查找标签的id属性
print(tag.attrs)  # 查找标签所有的属性,返回一个字典(属性名:属性值)

'''标签包的字符串'''
tag = soup.title
print(tag.string)  # 获取标签里的字符串
tag.string.replace_with("哈哈")  # 字符串不能直接编辑,可以替换

'''子节点的操作'''
tag = soup.head
print(tag.title)  # 获取head标签后再获取它包含的子标签

'''contents 和 .children'''
tag = soup.body
print(tag.contents)  # 将标签的子节点以列表返回
print([child for child in tag.children])  # 输出和上面一样

'''descendants'''
tag = soup.body
[print(child_tag) for child_tag in tag.descendants]  # 获取所有子节点和子子节点

'''strings和.stripped_strings'''
tag = soup.body
[print(str) for str in tag.strings]  # 输出所有所有文本内容
[print(str) for str in tag.stripped_strings]  # 输出所有所有文本内容,去除空格或空行

'''.parent和.parents'''
tag = soup.title
print(tag.parent)  # 输出便签的父标签
[print(parent) for parent in tag.parents]  # 输出所有的父标签

'''.next_siblings 和 .previous_siblings
    查出所有的兄弟节点
'''

'''.next_element 和 .previous_element
    下一个兄弟节点
'''

'''find_all的keyword 参数'''
soup.find_all(id='link2')  # 查找所有包含 id 属性的标签
soup.find_all(href=re.compile("elsie"))  # href 参数,Beautiful Soup会搜索每个标签的href属性:
soup.find_all(id=True)  # 找出所有的有id属性的标签
soup.find_all(href=re.compile("elsie"), id='link1')  # 也可以组合查找
soup.find_all(attrs={"属性名": "属性值"})  # 也可以通过字典的方式查找

更多详细使用可参考:https://cuiqingcai.com/5548.html

 

要求

第一部分:

请分析作业页面,爬取已提交作业信息,并生成已提交作业名单,保存为英文逗号分隔的csv文件。文件名为:hwlist.csv 。
文件内容范例如下形式:
学号,姓名,作业标题,作业提交时间,作业URL
20194010101,张三,羊车门作业,2018-11-13 23:47:36.8,http://www.cnblogs.com/sninius/p/12345678.html

第二部分:

在生成的 hwlist.csv 文件的同文件夹下,创建一个名为 hwFolder 文件夹,为每一个已提交作业的同学,新建一个以该生学号命名的文件夹,将其作业网页爬取下来,并将该网页文件存以学生学号为名,“.html”为扩展名放在该生学号文件夹中。
 

正题

  之前打过CTF比赛,完成这样的爬虫还是挺简单的。以爬取羊车门问题的作业为例,以下是我解决这个问题的思路,欢迎大家向我提出问题,或者指出错误。
 
 
  我们将需要爬取的内容在页面中找到,他是下图这样的:

 

  分析一下他们的代码,我在浏览器中对应位置右键,然后点击检查元素,可以找到对应部分的代码。但是,直接查看当前网页的源码发现,里面并没有对应的代码。我猜测这里是根据服务器上的数据动态生成的这部分代码,所以我们需要找到数据文件,以便向服务器申请,得到这部分资源。

  在刚才查看元素的地方接着找数据文件,在Network里面的文件中很顺利的就找到了,并在报文中拿到了URL和请求方法。

  查看一下这个文件发现是JSON文件,那样的话难度就又降低了,因为Python中有json库,解析json的能力很强。可以直接将json转换为字典和列表类型。

   在这里我简单介绍一下数据解析的过程吧。首先,我将爬取到的json文本转换成某种数据类型,具体由数据决定,一般不是字典就是列表。查看类型发现是字典,且字典中有三个key值,而我们需要的key在一个叫data的key中。

  而data中的数据是一个学生信息的列表类型,列表的每个元素都是一个字典,包括学生姓名,学号等信息。可以利用下标获取列表元素,并用key值得到你想拿到的信息。比如,利用Url得到网页链接。

 

 

 

这时候我们爬取需要的信息的准备工作可以说是结束了,我们拿到了数据的URL,并且知道了数据类型和数据结构。于是,我们只需要用requests库爬一下这个页面,然后用json解析一下,并且筛选有用的信息就好了。

(没用到BeautifulSoup和re库有点小失落)

 接下来就是创建文件,就没有什么难度了。只是在为每个学生创建文件的时候注意一下,创建好以后及时的回到上层目录,否则,可能会让文件一层层的嵌套下去。

代码

# -*- coding:utf-8 -*-

import requests
import json
import os
#抓取页面
url = 'https://edu.cnblogs.com/Homework/GetAnswers?homeworkId=2420&_=1542959851766'
try:
    r = requests.get(url,timeout=20)
    r.raise_for_status()
    r.encoding = r.apparent_encoding
except:
    print('网络异常或页面未找到,请重试')

#利用json拿到数据列表,每个列表元素都是字典
datas = json.loads(r.text)['data']
result = ""
#数据处理
for data in datas:
    result += str(data['StudentNo'])+','+data['RealName']+','+data['DateAdded'].replace('T',' ')+','+data['Title']+','+data['Url']+'\n'
#写入文件
with open('hwlist.csv','w') as f:
    f.write(result)
#创建文件夹hwFolder
os.mkdir('hwFolder')
os.chdir('hwFolder')
#创建每个学生的作业文件
for data in datas:
    #创建目录
    os.mkdir(str(data['StudentNo']))
    os.chdir(str(data['StudentNo']))
    #抓取页面
    try:
        webmsg = requests.get(data['Url'],timeout=20)
        webmsg.raise_for_status()
        webmsg.encoding = webmsg.apparent_encoding
    except:
        print('网络异常或页面未找到,请重试') 
    #保存抓到的页面
    with open(str(data['StudentNo'])+'.html','wb') as f:
        f.write(webmsg.content)
    os.chdir(os.path.pardir)

 

部分结果展示

 

 

上图是hwlist.csv文件的部分结果(Excel下打开)

玩个稍复杂点的

  像之前那样爬取页面的话,其实是有点问题的。首先,我们只是爬取了页面的内容,但是并没有抓取到页面的样式,页面显示会不太正常,排版混乱。其次,页面中还有图片等元素都不会显示出来。而且,如果遇到网络问题代码需要再次运行的时候还会遇到一个问题,那就是目录已经存在了,我们在创建目录就会失败。除此之外还是有不少问题的,此处我先解决之前说到的几个问题。即显示问题和目录问题。

  如何解决我提到的这些问题呢,目录问题我使用了一种比较容易实现的方案,那就是先判断当前目录是否存在,如果不存在就创建目录,否则就什么都不做。至于文件,暂定的方法是直接覆盖。显示问题也比较好解决,抓取网页和抓取样式或者网页其实都一样,就是用URL发送一个请求,来获得这个资源,其实和抓取HTML相比,就是文件格式不太一致。

  以抓取样式表(CSS)为例,样式的URL怎么获取呢?有一些样式是在一个叫做Link的标签的href属性里,这里面就是外联样式存储的位置。把它提取出来,请求这个样式,并且修改原来的href属性为抓到的文件在自己电脑上的保存位置即可。这样的话即可保证抓到的CSS可以正常使用,确保排版正确。

  当然了,即使这样,和原本的网页也是有差别的,因为抓取到的资源还是不够,和浏览器中获得的元素对比一下就会发现还差不少。鉴于本人能力有限,这里就补充一下爬取外联CSS和图片的内容,感兴趣的可以看一看。

  Tips:这里解析HTML页面借助了强大的BeautifulSoup4库(解析标签和玩一样)和re库,使工作量减少了不少。(安装bs4库: pip install BeautifulSoup4)

 

# -*- coding:utf-8 -*-

import requests
import json
import os
import re
from bs4 import BeautifulSoup

def getHtml(url,timeout=110):
    try:
        res = requests.get(url,timeout)
        res.raise_for_status()
        res.encoding = res.apparent_encoding
        return res
    except:
        print('网络异常,'+url+"爬取失败")
    
def saveFile(name,content,mode='w'):
    try:
        with open(name,mode) as f:
            f.write(content)
    except:
        print("文件"+name+"创建失败")
def getSource(text):
    #抓取样式
    root_url = 'https://www.cnblogs.com'
    soup = BeautifulSoup(text,'html.parser')
    for i in soup('link'):
        css_list = [css for css in i['href'].split('/') if 'css' in css]
        if css_list!=[]:
            filename = re.search(r'.*css',css_list[0]).group(0)
            r = requests.get(root_url+i['href'])
            saveFile(filename,r.content,'wb')
            text = text.replace(i['href'],'Source/'+filename)
    #抓取图片  用户自己插入的图片和网站自己生成的图片都抓
    #用户自己插的那些格式很乱……用户自己搞的东西就是个坑
    for i in soup('img'):
        try:
            img_list = [img for img in i['src'].split('/') if 'gif' in img or 'png' in img or 'jpeg' in img]
        except KeyError :#某用户自己改了HTML代码 得让我单独判断一下
            img_list = []
        if img_list!=[]:
            filename = img_list[0]
            try:
                r = requests.get(root_url+i['src'])
                r.raise_for_status()
            except:
                if not 'http' in i['src']:
                    r = requests.get("https:"+i['src'])
                else:#又是某用户写博客用了HTML编辑器,写的还不对
                    r = requests.get(i['src'])
            saveFile(filename,r.content,'wb')
            text = text.replace(i['src'],'Source/'+filename)
    #text用于修改原始的页面链接,保证本地可以正常查看页面
    return text
#############################主程序############################
#抓取页面 并得到数据
r = getHtml('https://edu.cnblogs.com/Homework/GetAnswers?homeworkId=2420&_=1542959851766')
datas = json.loads(r.text)['data']
#处理数据并将数据写入文件
result = ""
for data in datas:
    result += str(data['StudentNo'])+','+data['RealName']+','+data['DateAdded'].replace('T',' ')+','+data['Title']+','+data['Url']+'\n'
saveFile('hwlist.csv',result,'w')
#创建文件夹hwFolder
if not os.path.exists('hwFolder'):
    os.mkdir('hwFolder')
os.chdir('hwFolder')
#创建每个学生的作业文件
for data in datas:
    #创建目录
    if not os.path.exists(str(data['StudentNo'])):
        os.mkdir(str(data['StudentNo']))
    os.chdir(str(data['StudentNo']))
    #抓取页面
    webmsg = requests.get(data['Url'])
    print('当前的URL:'+data['Url'])#等待的过程有字出来不会无聊
    #页面的一些资源
    if not os.path.exists('Source'):
        os.mkdir('Source')
    os.chdir('Source')
    webtext = getSource(webmsg.text)
    os.chdir(os.path.pardir)
    saveFile(str(data['StudentNo'])+'.html',webtext.encode(),'wb')
    
    os.chdir(os.path.pardir)

   如果你的网络没问题,讲道理,应该不会抛异常。接下来找个页面看看效果吧:

排版抓出来了,挺炫的,当然,图片也抓了。

 

 

考虑到有人会HTML,我把被程序调整后的HTML代码找一个给大家看看,箭头指向的地方都是程序自己改过的地方:

 

 

 其实,虽然现在又和原页面接近了不少,但是……我暂时没有时间继续完善了,以后还会继续完善。给大家一个原博客的图片,你会我先我还是少了些东西。暂时先这样吧。

 

posted @ 2020-01-01 15:52  ABDM  阅读(277)  评论(0编辑  收藏  举报