一, 正则解析
常用正则表达式回顾:
单字符: . : 除换行以外所有字符 [] :[aoe] [a-w] 匹配集合中任意一个字符 \d :数字 [0-9] \D : 非数字 \w :数字、字母、下划线、中文 \W : 非\w \s :所有的空白字符包,括空格、制表符、换页符等等。等价于 [ \f\n\r\t\v]。 \S : 非空白 数量修饰: * : 任意多次 >=0 + : 至少1次 >=1 ? : 可有可无 0次或者1次 {m} :固定m次 hello{3,} {m,} :至少m次 {m,n} :m-n次 边界: $ : 以某某结尾 ^ : 以某某开头 分组: (ab) 贪婪模式: .* 非贪婪(惰性)模式: .*? re.I : 忽略大小写 re.M :多行匹配 re.S :单行匹配 re.sub(正则表达式, 替换内容, 字符串)
爬取糗事百科指定页面的糗图,并将其保存到指定文件夹中
import requests import os import re url = "https://www.qiushibaike.com/pic/page/%s/" headers = { "User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.119 Safari/537.36" } start_page = int(input("start_page: ")) end_page = int(input("end_page: ")) if not os.path.exists("qiushi"): os.mkdir("qiushi") for page in range(start_page,end_page+1): new_url = format(url%page) response = requests.get(url=new_url,headers=headers).text # <div class="thumb"> # # <a href="/article/121563020" target="_blank"> # <img src="//pic.qiushibaike.com/system/pictures/12156/121563020/medium/6D87JVF07YPCMKYV.jpg" alt="你并没有发现有个哥斯拉"> # </a> # # </div> # 匹配出以上标签,分组优先 e = '<div class="thumb">.*?<img src="(.*?)".*?>.*?</div>' img_url_list = re.findall(e,response,re.S) print(img_url_list) for img_url in img_url_list: img_url = "https:" + img_url img_name = img_url.split("/")[-1] img_path = "qiushi/" + img_name img_response = requests.get(url=img_url,headers=headers).content with open(img_path,"wb") as f: f.write(img_response)
二, bs4 BeautifulSoup解析
安装
pip install bs4 pip install lxml #需要的第三方库
基础使用
使用流程: - 导包:from bs4 import BeautifulSoup - 使用方式:可以将一个html文档,转化为BeautifulSoup对象,然后通过对象的方法或 者属性去查找指定的节点内容 (1)转化本地文件: - soup = BeautifulSoup(open('本地文件'), 'lxml') (2)转化网络文件: - soup = BeautifulSoup('字符串类型或者字节类型', 'lxml') (3)打印soup对象显示内容为html文件中的内容 基础巩固: (1)根据标签名查找 - soup.a 只能找到第一个符合要求的标签 (2)获取属性 - soup.a.attrs 获取a所有的属性和属性值,返回一个字典 - soup.a.attrs['href'] 获取href属性 - soup.a['href'] 也可简写为这种形式 (3)获取内容 - soup.a.string - soup.a.text - soup.a.get_text() 【注意】如果标签还有标签,那么string获取到的结果为None,而其它两个,可以获取文本内容 (4)find:找到第一个符合要求的标签 - soup.find('a') 找到第一个符合要求的 - soup.find('a', title="xxx") - soup.find('a', alt="xxx") - soup.find('a', class_="xxx") - soup.find('a', id="xxx") (5)find_all:找到所有符合要求的标签 - soup.find_all('a') - soup.find_all(['a','b']) 找到所有的a和b标签 - soup.find_all('a', limit=2) 限制前两个 (6)根据选择器选择指定的内容 select:soup.select('#feng') - 常见的选择器:标签选择器(a)、类选择器(.)、id选择器(#)、层级选择器 - 层级选择器: div .dudu #lala .meme .xixi 下面好多级 div > p > a > .lala 只能是下面一级 【注意】select选择器返回永远是列表,需要通过下标提取指定的对象
使用bs4实现将诗词名句网站中三国演义小说的每一章的内容爬去到本地磁盘进行存储 http://www.shicimingju.com/book/sanguoyanyi.html
import requests from bs4 import BeautifulSoup url = "http://www.shicimingju.com/book/sanguoyanyi.html" headers = { "User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.119 Safari/537.36" } page_text = requests.get(url=url,headers=headers).text soup = BeautifulSoup(page_text,'lxml') a_list = soup.select(".book-mulu > ul > li > a") fp = open("sanguo.txt","w",encoding="utf-8") for a in a_list: title = a.string # 获取a标签的内容 detail_url = 'http://www.shicimingju.com'+a['href'] detail_page_text = requests.get(url=detail_url,headers=headers).text soup = BeautifulSoup(detail_page_text,'lxml') content = soup.find('div',class_='chapter_content').text fp.write(title+"\\n"+content) fp.close()
三, xpath解析
- 环境安装
pip install lxml
- 解析原理
- 获取页面源码数据
- 实例化一个etree的对象,并且将页面源码数据加载到该对象中
- 调用该对象的xpath方法进行指定标签的定位
- xpath函数必须结合着xpath表达式进行标签定位和内容捕获
举例展示xpath相关语法:
<html lang="en"> <head> <meta charset="UTF-8" /> <title>测试bs4</title> </head> <body> <div> <p>百里守约</p> </div> <div class="song"> <p>李清照</p> <p>王安石</p> <p>苏轼</p> <p>柳宗元</p> <a href="http://www.song.com/" title="赵匡胤" target="_self"> <span>this is span</span> 宋朝是最强大的王朝,不是军队的强大,而是经济很强大,国民都很有钱</a> <a href="" class="du">总为浮云能蔽日,长安不见使人愁</a> <img src="http://www.baidu.com/meinv.jpg" alt="" /> </div> <div class="tang"> <ul> <li><a href="http://www.baidu.com" title="qing">清明时节雨纷纷,路上行人欲断魂,借问酒家何处有,牧童遥指杏花村</a></li> <li><a href="http://www.163.com" title="qin">秦时明月汉时关,万里长征人未还,但使龙城飞将在,不教胡马度阴山</a></li> <li><a href="http://www.126.com" alt="qi">岐王宅里寻常见,崔九堂前几度闻,正是江南好风景,落花时节又逢君</a></li> <li><a href="http://www.sina.com" class="du">杜甫</a></li> <li><a href="http://www.dudu.com" class="du">杜牧</a></li> <li><b>杜小月</b></li> <li><i>度蜜月</i></li> <li><a href="http://www.haha.com" id="feng">凤凰台上凤凰游,凤去台空江自流,吴宫花草埋幽径,晋代衣冠成古丘</a></li> </ul> </div> </body> </html>
属性定位: #找到class属性值为song的div标签 //div[@class="song"] 层级&索引定位: #找到class属性值为tang的div的直系子标签ul下的第二个子标签li下的直系子标签a //div[@class="tang"]/ul/li[2]/a 逻辑运算: #找到href属性值为空且class属性值为du的a标签 //a[@href="" and @class="du"] 模糊匹配: //div[contains(@class, "ng")] //div[starts-with(@class, "ta")] 取文本: # /表示获取某个标签下的文本内容 # //表示获取某个标签下的文本内容和所有子标签下的文本内容 //div[@class="song"]/p[1]/text() //div[@class="tang"]//text() 取属性: //div[@class="tang"]//li[2]/a/@href
xpath语法:
1.下载:pip install lxml 2.导包:from lxml import etree 3.将html文档或者xml文档转换成一个etree对象,然后调用对象中的方法查找指定的节点 3.1 本地文件:tree = etree.parse(文件名) tree.xpath("xpath表达式") 3.2 网络数据:tree = etree.HTML(网页内容字符串) tree.xpath("xpath表达式")
- 在解析后持久化存储的时候,有可能出现乱码,编码不一致的情况,以下有两种解决方法
- response.encoding="utf-8"
- img_name = img_name.encode("iso-8859-1") [用乱码的数据.encode]
- 解析58二手房的相关数据
import requests from lxml import etree url = "https://bj.58.com/ershoufang/?PGTID=0d100000-0000-18cd-582d-127b1ae5e77c&ClickID=2" headers = { "User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.119 Safari/537.36" } # 获取页面信息 page_text = requests.get(url=url,headers=headers).text # 实例化etree对象,并将页面信息加载 tree = etree.HTML(page_text) # 根据获取到的页面信息分析,获取到li标签的列表(每个信息都存在li中) li_list = tree.xpath("//ul[@class='house-list-wrap']/li") # 这里将获取到的信息写入同一个文件中,所以文件在循环外边打开就可以了 fp = open("58.txt","w",encoding="utf-8") # 循环li_list,然后用li标签获取到房源的详细信息 for li in li_list: title = li.xpath("./div[@class='list-info']/h2/a/text()")[0] # /text()获取到列表里面只有一条数据 price = "".join(li.xpath("./div[@class='price']//text()")) # /text()获取到列表里面有多条数据,转换为字符串,方便写入文件 msg = "".join(li.xpath("./div[@class='list-info']/p[@class='baseinfo']//text()")) content = (title+":"+msg+price).replace(" ",'') # 将拼接好的字符串写入文件,使用=分割每条信息 fp.write(content+"\n"+"="*100+"\n") # 最后不要忘了关闭文件 fp.close()
- 解析图片数据:http://pic.netbian.com/4kmeinv/
import requests import os import urllib from lxml import etree url = "http://pic.netbian.com/4kmeishi/" headers = { "UserAgent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.119 Safari/537.36" } page_text = requests.get(url=url,headers=headers).text tree = etree.HTML(page_text) li_list = tree.xpath("//div[@class='slist']/ul/li") # 将图片保存在文件夹,判断文件夹是否存在,不存在就创建 if not os.path.exists("meishi"): os.mkdir("meishi") for li in li_list: img_name = li.xpath("./a/b/text()")[0] # 运行后发现图片名字都是乱码,然后可以使用以下方式解决,查看网站的编码方式是gbk,所以要解码成gbk img_name = img_name.encode("iso-8859-1").decode("gbk") img_url = "http://pic.netbian.com"+li.xpath("./a/img/@src")[0] img_path = "meishi/"+img_name+".jpg" urllib.request.urlretrieve(url=img_url,filename=img_path) print(img_name+"下载成功") print("over")
- 【重点】下载煎蛋网中的图片数据:http://jandan.net/ooxx (该网站使用的数据加密的反爬机制)
请求煎蛋网的时候,我们打开network,查看response响应,找到我们要爬取的第一张图片的地址,然后再对比第二张图片的地址,发现地址都是一样的,这说明网站很有可能是使用了数据加密的反爬机制
然后我们再往后看有个span标签,对应内容有个哈希值,复制span的class名,在本文件中找,发现都是图片后边跟着的,然后我们再去全局中找一下,真的被我们发现了还有一个文件中有
查看这个函数,我们没有发现加密方式,但是发现好像是调用了一个函数,然后我们复制函数名(jdtPGUg7oYxbEGFASovweZE267FFvm5aYz),再去找一下
果然,在本页还是被我们找到了这个函数,仔细看这个函数,里边提到了我们熟悉的两种加密方式,一个是md5,一个是base64,当然我们知道md5是不可逆的,所以这个网站很有可能就是用的base64的方式加密的,那我们就来试一下使用base64解密数据,如果成功了,就说明我们分析的没错,下面上代码
import os import requests import urllib import base64 from lxml import etree url = "http://jandan.net/ooxx" headers = { "User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.119 Safari/537.36" } page_text = requests.get(url=url,headers=headers).text if not os.path.exists("jiandan"): os.mkdir("jiandan") tree = etree.HTML(page_text) img_hash_list = tree.xpath("//span[@class='img-hash']/text()") for img_hash in img_hash_list: # 使用base64解密 img_url = "http:" + base64.b64decode(img_hash).decode() img_name = img_url.split("/")[-1] img_path = "jiandan/"+img_name urllib.request.urlretrieve(url=img_url,filename=img_path) print(img_name+"下载完成") print("over")
- 爬取站长素材中的简历模板 http://sc.chinaz.com/jianli/free.html
我们在查看该网页的network的时候发现第一页固定的url,从第二页开始,页码是动态的,所以在开始的时候我们要针对这个情况做一个判断
选择一个模板查看详情,会看到下载的时候让我们自己选择使用什么方式下载,这里避免使用同一个路径请求次数过多,我们用random模块,随机抽取里面的一个进行下载
如果请求量比较大的话,有可能会报一个HTTPConnec.......的错误,可以用下面的方法解决:
- 第一种是利用time模块的sleep方法,让程序运行过程中睡几秒,等内容及加载出来再往下执行,
- 另一种方法是可以在headers中添加一个键值对: 'Connection':'close', #当请求成功后,马上断开该次请求(及时释放请求池中的资源)
import os import requests import random from lxml import etree if not os.path.exists("jianli"): os.mkdir("jianli") headers = { # 'Connection':'close', #当请求成功后,马上断开该次请求(及时释放请求池中的资源),如果报错的话可以考虑 "User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.119 Safari/537.36" } url = "http://sc.chinaz.com/jianli/free_%d.html" for page in range(1,3): if page == 1: new_url = "http://sc.chinaz.com/jianli/free.html" else: new_url = format(url % page) response = requests.get(url=new_url,headers=headers) response.encoding = "utf-8" page_text = response.text tree = etree.HTML(page_text) # 内容存在一个个div中,我们先获取div div_list = tree.xpath("//div[@id='container']/div") for div in div_list: name = div.xpath("./a/img/@alt")[0] detail_url = div.xpath("./a/@href")[0] detail_page = requests.get(url=detail_url,headers=headers).text detail_tree = etree.HTML(detail_page) # 获取下载链接列表 download_list = detail_tree.xpath("//div[@class='clearfix mt20 downlist']/ul/li/a/@href") # 随机获取一个下载通道 download_url = random.choice(download_list) data = requests.get(url=download_url,headers=headers).content filename = name+".rar" with open("jianli/"+filename,'wb') as f: f.write(data) print(name+"下载成功") print("over")
- #解析所有的城市名称 https://www.aqistudy.cn/historydata/
page_text = requests.get(url=url,headers=headers).text tree = etree.HTML(page_text) # 有多个满足条件的,可以用管道符| 表示或者 li_list = tree.xpath("//div[@class='bottom']/ul/li | //div[@class='bottom']/ul/div[2]/li") fp = open("chengshi.txt",'w',encoding="utf-8") for li in li_list: city_name = li.xpath("./a/text()")[0] fp.write(city_name+"\n") fp.close()