Python网络爬虫 第二章 数据解析
一、数据解析概述
在上?章中, 我们基本上掌握了抓取整个??的基本技能. 但是呢, ?多数情况下, 我们并不需要整个??的内容, 只是需要那么??部分.
怎么办呢? 这就涉及到了数据提取的问题.
本课程中, 提供三种解析?式:
- 1. re解析
- 2. bs4解析
- 3. xpath解析
这三种?式可以混合进?使?, 完全以结果做导向, 只要能拿到你想要的数据. ?什么?案并不重要. 当你掌握了这些之后. 再考虑性能的问题
二、正则表达式
Regular Expression, 正则表达式, ?种使?表达式的?式对字符串进?匹配的语法规则.
我们抓取到的??源代码本质上就是?个超?的字符串, 想从??提取内容.?正则再合适不过了.
正则的优点: 速度快, 效率?, 准确性? 正则的缺点: 新?上?难度有点??.
不过只要掌握了正则编写的逻辑关系, 写出?个提取??内容的正则其实并不复杂
https://github.com/cdoco/learn-regex-zh
正则的语法: 使?元字符进?排列组合?来匹配字符串 在线测试正则表达式https://tool.oschina.net/regex/
元字符: 具有固定含义的特殊符号
常?元字符:
. 匹配除换?符以外的任意字符 \w 匹配字?或数字或下划线 \s 匹配任意的空?符 \d 匹配数字 \n 匹配?个换?符 \t 匹配?个制表符 ^ 匹配字符串的开始 $ 匹配字符串的结尾 \W 匹配?字?或数字或下划线 \D 匹配?数字 \S 匹配?空?符 a|b 匹配字符a或字符b () 匹配括号内的表达式,也表示?个组 [...] 匹配字符组中的字符 [^...] 匹配除了字符组中字符的所有字符
量词: 控制前?的元字符出现的次数
* 重复零次或更多次 + 重复?次或更多次 ? 重复零次或?次 {n} 重复n次 {n,} 重复n次或更多次 {n,m} 重复n到m次
贪婪匹配和惰性匹配
.* 贪婪匹配
.*? 惰性匹配
这两个要着重的说?下. 因为我们写爬??的最多的就是这个惰性匹配.
先看案例
str: 玩?吃鸡游戏, 晚上?起上游戏, ?嘛呢? 打游戏啊 reg: 玩?.*?游戏 此时匹配的是: 玩?吃鸡游戏 reg: 玩?.*游戏 此时匹配的是: 玩?吃鸡游戏, 晚上?起上游戏, ?嘛呢? 打游戏 str:胡辣汤reg: <.*> 结果:胡辣汤str:胡辣汤reg: <.*?> 结果:str:胡辣汤饭团 reg:.*?结果:胡辣汤
所以我们能发现这样?个规律: .? 表示尽可能少的匹配, .表示尽可能多的匹配, 暂时先记住这个规律. 后?写爬?会?到的哦
三、re模块
那么接下来的问题是, 正则我会写了, 怎么在python程序中使?正则呢? 答案是re模块
re模块中我们只需要记住这么?个功能就?够我们使?了.
1. findall 查找所有. 返回list
lst = re.findall("m", "mai le fo len, mai ni mei!") print(lst) # ['m', 'm', 'm'] #'r'是防止字符转义的 如果路径中出现'\t'的话 不加r的话\t就会被转义 而加了'r'之后'\t'就能保留原有的样子 lst = re.findall(r"\d+", "5点之前. 你要给我5000万") print(lst) # ['5', '5000']
2. search 会进?匹配. 但是如果匹配到了第?个结果. 就会返回这个结果. 如果匹配不上search返回的则是None
ret = re.search(r'\d', '5点之前. 你要给我5000万').group() print(ret) # 5
3. match 只能从字符串的开头进?匹配
ret = re.match('a', 'abc').group() print(ret) # a
4. finditer, 和findall差不多. 只不过这时返回的是迭代器(重点)
it = re.finditer("m", "mai le fo len, mai ni mei!") for el in it: print(el.group()) # 依然需要分组
5. compile() 可以将?个??的正则进?预加载. ?便后?的使?
obj = re.compile(r'\d{3}') # 将正则表达式编译成为?个正则表达式对象, 规则要匹配的是3个数字 ret = obj.search('abc123eeee') # 正则表达式对象调?search, 参数为待匹配的字符串 print(ret.group()) # 结果: 123
6. 正则中的内容如何单独提取?
单独获取到正则中的具体内容可以给分组起名字
import re
s = """郭麒麟宋铁大聪明范思哲胡说八道""" # (?P<分组名字>正则) 可以单独从正则匹配的内容中进一步提取内容 obj = re.compile(r"(?P", re.S) # re.S: 让.能匹配换行符 result = obj.finditer(s) for it in result: print(it.group("wahaha")) print(it.group("id")).*?)
这?可以看到我们可以通过使?分组. 来对正则匹配到的内容进?步的进?筛选.
关于正则, 还有?个重要的?点, 也?常的简单, 在本节中就不继续扩展了. 下??节的案例中会把这个?点进?简单的介绍.
四、?刃?瓣TOP250电影信息
终于可以放开?脚??番事业了. 今天我们的?标是?瓣电影
TOP250排?榜. 没别的意思, 练??已
先看需求:
?标: 抓取"电影名称","上映年份","评分","评分?数"四项内容.
怎么做呢? ?先, 先看?下??源代码. 数据是否是直接怼在源代码上的?
很明显, 我们想要的数据全部都在??源代码中体现了. 所以, 我们不需要考虑js动态加载数据的情况了. 那么接下来就是编写爬?代码的
第?步了. 拿到??源代码:
import requests headers = { "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"
} url = "https://movie.douban.com/top250?start=0&filter=" resp = requests.get(url, headers=headers) print(resp.text)
然后呢. 从??源代码中提取我们需要的内容. 这时候我们就可以去写正则了
# 解析数据 obj = re.compile(r'.*? .*?(?P.*?) ' r'.*?.*?
(?P.*?) .*?' r'class="rating_num" property="v:average">(?P .*?) .*?' r'(?P.*?)人评价 ', re.S)开始匹配, 将最终完整的数据按照??喜欢(需要)的?式写??件.
# 拿到页面源代码. requests # 通过re来提取想要的有效信息 re import requests import re import csv url = "https://movie.douban.com/top250" headers = { "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.192 Safari/537.36" } resp = requests.get(url, headers=headers) page_content = resp.text # 解析数据 obj = re.compile(r'.*? .*?(?P.*?) ' r'.*?.*?
(?P.*?) .*?' r'class="rating_num" property="v:average">(?P .*?) .*?' r'(?P.*?)人评价 ', re.S) # 开始匹配 result = obj.finditer(page_content) f = open("data.csv", mode="w") #创建csv文件写入工具,也可以直接f.write() csvwriter = csv.writer(f) for it in result: # print(it.group("name")) # print(it.group("score")) # print(it.group("num")) # print(it.group("year").strip()) dic = it.groupdict() # strip() 方法用于移除字符串头尾指定的字符(默认为空格) dic['year'] = dic['year'].strip() csvwriter.writerow(dic.values()) f.close() print("over!")五、bs4解析-HTML语法
bs4解析?较简单, 但是呢, ?先你需要了解?丢丢的html知识. 然后再去使?bs4去提取, 逻辑和编写难度就会?常简单和清晰
HTML(Hyper Text Markup Language)超?本标记语?, 是我们编写??的最基本也是最核?的?种语?. 其语法规则就是?不同的标签对??上的内容进?标记, 从?使??显示出不同的展示效果.<h1> 我爱你 h1>上述代码的含义是在??中显示"我爱你"三个字, 但是我爱你三个字被"
"和"
"标记了. ?话就是被括起来了. 被H1这个标签括起来了. 这个时候. 浏览器在展示的时候就会让我爱你变粗变?. 俗称标题, 所以HTML的语法就是?类似这样的标签对??内容进?标记.不同的标签表现出来的效果也是不?样的.h1: ?级标题 h2: ?级标题 p: 段落 font: 字体(被废弃了, 但能?) body: 主体
这?只是给??们简单科普?下, 其实HTML标签还有很多很多的.
我们不需要??列举(这是爬?课, 不是前端课).
OK~ 标签我们明?了, 接下来就是属性了.<h1> 我爱你 h1> <h1 align='right'> 我爱你妹 h1>有意思了. 我们发现在标签中还可以给出xxx=xxx这样的东?. 那么它?是什么呢? ?该如何解读呢?
?先, 这两个标签都是h1标签, 都是?级标题, 但是下?这个会显示在右边. 也就是说, 通过xxx=xxx这种形式对h1标签进?步的说明了.
那么这种语法在html中被称为标签的属性. 并且属性可以有很多个.
例如:<body text="green" bgcolor="#eee"> 你看我的颜?. 贼健康 body>总结, html语法:
<标签 属性="值" 属性="值"> 被标记的内容 标签>有了这些知识, 我们再去看bs4就会得?应?了. 因为bs4就是通过标签和属性去定位??上的内容的。
六、bs4模块安装和使?
bs4模块安装
在python中我?般只推荐?pip进?安装. 原因: 简单!!!!pip install bs4如果安装的速度慢, 建议更换国内源(推荐阿?源或者清华源)
如何使?bs4pip install -i https://pypi.tuna.tsinghua.edu.cn/simple bs4bs4在使?的时候就需要参照?些html的基本语法来进?使?了. 我们直接上案例哈. 案例是最能直观的展现出bs4的便捷效果的.
我们来尝试抓取北京新发地市场的农产品价格. http://www.xinfadi.com.cn/marketanalysis/0/list/1.shtml?规矩, 先获取??源代码. 并且确定数据就在??源代码中~
import requests from bs4 import BeautifulSoup resp =requests.get("http://www.xinfadi.com.cn/marketanalysis/0/list/1.shtml") print(resp.text)将??源代码丢给BeautifulSoup, 然后我们就可以通过bs对象去检索??源代码中的html标签了
page = BeautifulSoup(resp.text)BeautifulSoup对象获取html中的内容主要通过两个?法来完成
find()
find_all()基本上有这两个?法就够?了. 其他的可以??进?英?翻译就知道啥意思了.
不论是find还是find_all 参数?乎是?致的.
语法:
find(标签, 属性=值)
意思是在??中查找 xxx标签, 并且标签的xxx属性必须是xxx值 例:
find('div', age=18) 含义: 在??中查找div标签, 并且属性age必须是18的这个标签.
find_all()的?法和find()?乎?致.
- find()查找1个.
- find_all()查找??中所有的.
但是这种写法会有些问题. ?如html标签中的class属性.
class="honor"> page.find("div", class="honor") # 注意, python中class是关键字. 会报错的. 怎么办呢? 可以在class后?加个下划线 page.find("div", class_="honor") #我们可以使?第?种写法来避免这类问题出现 page.find("div", attrs={"class": "honor"})好了, ?法说完了. 接下来就回来看怎么抓取新发地的价格吧
table = page.find("table", class_="hq_table") print(table)接下来就可以进?步去提取数据了. 后?的直接给出完整代码.
因为逻辑都是?样的. 并没有多么的复杂, 过程就省略了.最后代码
# 安装 # pip install bs4 -i 清华 # 1. 拿到页面源代码 # 2. 使用bs4进行解析. 拿到数据 import requests from bs4 import BeautifulSoup import csv url = "http://www.xinfadi.com.cn/marketanalysis/0/list/1.shtml" resp = requests.get(url) f = open("菜价.csv", mode="w") csvwriter = csv.writer(f) # 解析数据 # 1. 把页面源代码交给BeautifulSoup进行处理, 生成bs对象 # html.parser 告诉解析器这是html文件 page = BeautifulSoup(resp.text, "html.parser") # 指定html解析器 # 2. 从bs对象中查找数据 # find(标签, 属性=值) # find_all(标签, 属性=值) # table = page.find("table", class_="hq_table") # class是python的关键字 table = page.find("table", attrs={"class": "hq_table"}) # 和上一行是一个意思. 此时可以避免class # 拿到所有数据行tr # 行tr 列td print(table) #做切片 从第一个开始切 排除了第0个表头 获得纯数据 trs = table.find_all("tr")[1:] for tr in trs: # 每一行 tds = tr.find_all("td") # 拿到每行中的所有td name = tds[0].text # .text 表示拿到被标签标记的内容 low = tds[1].text # .text 表示拿到被标签标记的内容 avg = tds[2].text # .text 表示拿到被标签标记的内容 high = tds[3].text # .text 表示拿到被标签标记的内容 tp = tds[4].text # .text 表示拿到被标签标记的内容 kind = tds[5].text # .text 表示拿到被标签标记的内容 date = tds[6].text # .text 表示拿到被标签标记的内容 csvwriter.writerow([name, low, avg, high, tp, kind, date]) f.close() print("over1!!!!")七、bs4抓取图片
为了视频和?档能够正常投放在市?上, 这里抓取的图?都是唯美桌?系
https://www.umei.cc/bizhitupian/weimeibizhi/注意我选中的这个区域, 我们想要的图?就在这?. 但是, 绝对不是现在你看到的样?. 为什么呢? 不够?清?图~
真正的?清?图在???中, ?如, 我点击第?个图?这才是我想要的?图~
也就是说, 我需要在?站的??中, 找到???的链接, 然后请求到???, 才能看到这张?图~ 不明?的, 把上?的内容重新梳理?下!!!!!!!
也就是说, 想要下载该?站图?(?清?图), 需要三步,
- 第?步, 在主??中拿到每?个图?的???链接
- 第?步, 在???中找到真正的图?下载地址
- 第三步, 下载图?
# 1.拿到主页面的源代码. 然后提取到子页面的链接地址, href # 2.通过href拿到子页面的内容. 从子页面中找到图片的下载地址 img -> src # 3.下载图片 import requests from bs4 import BeautifulSoup import time url = "https://www.umei.cc/bizhitupian/weimeibizhi/" resp = requests.get(url) resp.encoding = 'utf-8' # 处理乱码 # print(resp.text) # 把源代码交给bs main_page = BeautifulSoup(resp.text, "html.parser") alist = main_page.find("div", class_="TypeList").find_all("a") # print(alist) for a in alist: href = a.get('href') # 直接通过get就可以拿到属性的值 # 拿到子页面的源代码 # 从子页面中拿到图片的下载路径 child_page_resp = requests.get(href) child_page_resp.encoding = 'utf-8' child_page_text = child_page_resp.text child_page = BeautifulSoup(child_page_text, "html.parser") p = child_page.find("p", align="center") img = p.find("img") # 图片url src = img.get("src") # 下载图片 img_resp = requests.get(src) # img_resp.content # 这里拿到的是字节 # http://kr.shanghai-jiuxin.com/file/2020/1031/6b72c57a1423c866d2b9dc10d0473f27.jpg # 6b72c57a1423c866d2b9dc10d0473f27.jpg img_name = src.split("/")[-1] # 拿到url中的最后一个/以后的内容 with open("img/"+img_name, mode="wb") as f: f.write(img_resp.content) # 图片内容写入文件 print("over!!!", img_name) time.sleep(1) print("all over!!!")八、Xpath解析
XPath是??在 XML ?档中查找信息的语?. XPath可?来在 XML?档中对元素和属性进?遍历. ?我们熟知的HTML恰巧属于XML的?个?集. 所以完全可以?xpath去查找html中的内容.
详细说明见这篇博客https://www.jianshu.com/p/85a3004b5c06
?先, 先了解?个概念.在上述html中,<book> <id>1id> <name>野花遍地?name> <price>1.23price> <author> <nick>周?强nick> <nick>周芷若nick> author> book>
- book, id, name, price....都被称为节点.
- Id, name, price, author被称为book的?节点
- book被称为id, name, price, author的?节点
- id, name, price,author被称为同胞节点
OK~ 有了这些基础知识后, 我们就可以开始了解xpath的基本语法了
在python中想要使?xpath, 需要安装lxml模块pip install lxml?法:
1. 将要解析的html内容构造出etree对象.
2. 使?etree对象的xpath()?法配合xpath表达式来完成对数据的提取xpath语法
表达式 描述 nodename 选取此节点的所有子节点。 / 从根节点选取。 // 从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置。 . 选取当前节点。 .. 选取当前节点的父节点。 @ 选取属性。 from lxml import etree xml = """""" tree = etree.XML(xml) # result = tree.xpath("/book") # /表示层级关系. 第一个/是根节点 # result = tree.xpath("/book/name") # result = tree.xpath("/book/name/text()") # text() 拿文本 # result = tree.xpath("/book/author//nick/text()") # // 后代 # result = tree.xpath("/book/author/*/nick/text()") # * 任意的节点. 通配符(会儿) result = tree.xpath("/book//nick/text()") print(result) 1 野花遍地香 1.23 臭豆腐 周大强 周芷若 周杰伦 蔡依林 热热热热热1 热热热热热2 胖胖陈 胖胖不陈 xpath如何提取属性信息. 我们上?段真实的HTML来给各位讲解?下
准备HTML:DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>Titletitle> head> <body> <ul> <li><a href="http://www.baidu.com">百度a>li> <li><a href="http://www.google.com">谷歌a>li> <li><a href="http://www.sogou.com">搜狗a>li> ul> <ol> <li><a href="feiji">飞机a>li> <li><a href="dapao">大炮a>li> <li><a href="huoche">火车a>li> ol> <div class="job">李嘉诚div> <div class="common">胡辣汤div> body> html>
from lxml import etree tree = etree.parse("b.html") # result = tree.xpath('/html') # result = tree.xpath("/html/body/ul/li/a/text()") # result = tree.xpath("/html/body/ul/li[1]/a/text()") # xpath的顺序是从1开始数的, []表示索引 # result = tree.xpath("/html/body/ol/li/a[@href='dapao']/text()") # [@xxx=xxx] 属性的筛选 # print(result) # ol_li_list = tree.xpath("/html/body/ol/li") # # for li in ol_li_list: # # 从每一个li中提取到文字信息 # result = li.xpath("./a/text()") # 在li中继续去寻找. 相对查找 # print(result) # result2 = li.xpath("./a/@href") # 拿到属性值: @属性 # print(result2) # # print(tree.xpath("/html/body/ul/li/a/@href")) print(tree.xpath('/html/body/div[1]/text()')) print(tree.xpath('/html/body/ol/li/a/text()'))如果页面过于复杂,但想要要获取xpath,可以借助谷歌浏览器的功能直接获取xpath
右击 检查