爬虫
###################################################
一、什么是爬虫?
爬虫:就是抓取网页数据的程序。
二、爬虫怎么抓取网页数据:
网页三大特征:
-1. 网页都有自己唯一的URL(统一资源定位符)来进行定位
-2. 网页都使用HTML(超文本标记语言)来描述页面信息。
-3. 网页都使用HTTP/HTTPS(超文本传输协议)协议来传输HTML数据。
爬虫的设计思路:
-1. 首先确定需要爬取的网页URL地址。
-2. 通过HTTP/HTTP协议来获取对应的HTML页面。
-3. 提取HTML页面里有用的数据:
a. 如果是需要的数据,就保存起来。
b. 如果是页面里的其他URL,那就继续执行第二步。
三、为什么选择Python做爬虫?
- PHP 虽然是世界上最好的语言,但是他天生不是干这个的,而且对多线程、异步支持不够好,并发处理能力很弱。 爬虫是工具性程序,对速度和效率要求比较高。
- Java 的网络爬虫生态圈也很完善,是Python爬虫最大的对手。但是Java语言本身很笨重,代码量很大。重构成本比较高,任何修改都会导致代码的大量变动。爬虫经常需要修改部分采集代码。
- C/C++ 运行效率和性能几乎最强,但是学习成本很高,代码成型比较慢。能用C/C++做爬虫,只能说是能力的表现,但是不是正确的选择。
- Python 语法优美、代码简洁、开发效率高、支持的模块多,相关的HTTP请求模块和HTML解析模块非常丰富。还有强大的爬虫Scrapy,以及成熟高效的 scrapy-redis分布式策略。 而且,调用其他借口也非常方便(胶水语言)
四、爬虫的整体介绍
-1. 如何抓取HTML页面:
HTTP请求的处理,urllib、urllib2、requests
处理后的请求可以模拟浏览器发送请求,获取服务器响应的文件
-2. 解析服务器响应的内容
re、xpath、BeautifulSoup4(bs4)、jsonpath、pyquery等
使用某种描述性一样来给我们需要提取的数据定义一个匹配规则,
符合这个规则的数据就会被匹配。
-3. 如何采集动态HTML、验证码的处理
通用的动态页面采集:Selenium + PhantomJS(无界面):模拟真实浏览器加载js、ajax等非静态页面数据
Tesseract:机器学习库,机器图像识别系统,可以处理简单的验证码,复杂的验证码可以通过手动输入/专门的打码平台
-4. Scrapy框架:(Scrapy,Pyspider)
高定制性高性能(异步网络框架twisted),所以数据下载速度非常快,
提供了数据存储、数据下载、提取规则等组件。
-5. 分布式策略 scrapy-reids:
scrapy-redis,在Scrapy的基础上添加了一套以 Redis 数据库为核心的组件。
让Scrapy框架支持分布式的功能,主要在Redis里做 请求指纹去重、请求分配、数据临时存储。
-6. 爬虫 - 反爬虫 - 反反爬虫 之间的斗争:
其实爬虫做到最后,最头疼的不是复杂的页面,也是晦涩的数据,而是网站另一边的反爬虫人员。
User-Agent、代理、验证码、动态数据加载、加密数据。
数据价值,是否值的去费劲做反爬虫。
1. 机器成本 + 人力成本 > 数据价值,就不反了,一般做到封IP就结束了。
2. 面子的战争....
爬虫和反爬虫之间的斗争,最后一定是爬虫获胜!
为什么?只要是真实用户可以浏览的网页数据,爬虫就一定能爬下来!
五、通用爬虫和聚焦爬虫
1、通用爬虫:搜索引擎用的爬虫系统。
-1目标:就是尽可能把互联网上所有的网页下载下来,放到本地服务器里形成备份,
再对这些网页做相关处理(提取关键字、去掉广告),最后提供一个用户检索接口。
-2抓取流程:
a) 首选选取一部分已有的URL,把这些URL放到待爬取队列。
b) 从队列里取出这些URL,然后解析DNS得到主机IP,然后去这个IP对应的服务器里下载HTML页面,保存到搜索引擎的本地服务器。
之后把这个爬过的URL放入已爬取队列。
c) 分析这些网页内容,找出网页里其他的URL连接,继续执行第二步,直到爬取条件结束。
-3 搜索引擎如何获取一个新网站的URL:
1. 主动向搜索引擎提交网址:http://zhanzhang.baidu.com/linksubmit/url
2. 在其他网站里设置网站的外链。
3. 搜索引擎会和DNS服务商进行合作,可以快速收录新的网站。
DNS:就是把域名解析成IP的一种技术。
-4 通用爬虫并不是万物皆可爬,它也需要遵守规则:
Robots协议:协议会指明通用爬虫可以爬取网页的权限。
Robots.txt 只是一个建议。并不是所有爬虫都遵守,一般只有大型的搜索引擎爬虫才会遵守。咱们个人写的爬虫,就不管了。
-5 通用爬虫工作流程:爬取网页 - 存储数据 - 内容处理 - 提供检索/排名服务
-6 搜索引擎排名:
1. PageRank值:根据网站的流量(点击量/浏览量/人气)统计,流量越高,网站也越值钱,排名越靠前。
2. 竞价排名:谁给钱多,谁排名就高。
-7 通用爬虫的缺点:
1. 只能提供和文本相关的内容(HTML、Word、PDF)等等,但是不能提供多媒体文件(音乐、图片、视频)和二进制文件(程序、脚本)等等。
2. 提供的结果千篇一律,不能针对不同背景领域的人提供不同的搜索结果。
3. 不能理解人类语义上的检索。
2、为了解决这个问题,聚焦爬虫出现了:
聚焦爬虫:爬虫程序员写的针对某种内容的爬虫。
面向主题爬虫,面向需求爬虫:会针对某种特定的内容去爬取信息,而且会保证信息和需求尽可能相关。
###################################################################
六、urllib库
1、简介
urllib库是Python提供的用于操作URL的模块,在2版本中,有urllib和urllib2两个库,在3版本中,只有urllib库,我们使用3系列。
2、方法和使用
urlllib.request 可以用来发送request和获取request的结果
rullib.parse用来解析和处理URL
1.urllib.request.urlopen方法
urlopen(url,data=None,context=None)
如果没有data则表示post请求。。。如果有则表示get请求
context表示的是https请求小区ssl错误。
如有有ssl错误,也可以使用以下代码解决:
import ssl
ssl._create_default_https_context = ssl._create_unverified_context
2.urllib.request.urlretrieve(url,file_path)
将url内容直接下载到file_path中。(一般是整个页面)
3.urllib.parse相关使用
a)urllib.parse.urlencode() #post方法
通过post提交的数据,都需要通过这个函数转码,并且发送请求的时候必须为字节格式。
使用例子:
data:是一个字典
rullib.parse.urlencode(data).encode('utf-8')
b)urllib.parse.quote() #get方法 编码
get参数中,有中文的,需要使用这个函数转码
使用例子:
http://www.baidu.com?name=中国
c)urllib.parse.unquote() #get方法 解码
3、HTTPResponse对象常见方法
1.read() #读取的是二进制数据
编码 encode() 字符串 --> 字节
解码 decode() 字节 --> 字符串
2.readline() #读取一行
readlines() #读取全部,返回一个列表
##1和2中方法,读取的都是字节类型,需要通过解码转化成字符串类型
3.getcode() #状态码
geturl() #获取url
getheaders() #响应头信息。列表里面有元组
status属性 #http状态码
4、例题
1 import urllib.request 2 3 url = "http://www.baidu.com" 4 # url = "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1532350126542&di=0b40d417ccdf5e360bec2879ea44caea&imgtype=0&src=http%3A%2F%2Fs3.sinaimg.cn%2Fmw690%2F006LDoUHzy7auXtCqn8c2%26690" 5 # url = "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1532350126542&di=0b40d417ccdf5e360bec2879ea44caea&imgtype=0&src=http%3A%2F%2Fs3.sinaimg.cn%2Fmw690%2F006LDoUHzy7auXtCqn8c2%26690" 6 # url = "https://www.365yg.com/a6579403582011867652/#mid=1582214612406285" 7 # url = "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1532363886029&di=bca0c1ae84babe8d1fa0d53c705c436e&imgtype=0&src=http%3A%2F%2Fr2.ykimg.com%2F05410508528FB37D6A0A4D4BF768EB12" 8 9 # 发送请求(url表示要请求的地址) 10 res = urllib.request.urlopen(url=url) 11 12 print(res,type(res)) #获取res 13 print(res.geturl()) #获取url 14 print(res.getheaders()) #获取请求头信息 15 print(res.status) #获取状态码 16 17 #将url内容直接下载到当前目录的filename文件中。 18 urllib.request.urlretrieve(url=url,filename="nice2.jpg") 19 urllib.request.urlretrieve(url=url,filename="baidu.html") 20 21 #在进行get提交时,如果有rul中有中文,使用quote进行编码 22 str = urllib.parse.quote("https://www.baidu.com/s?ie=utf-8&wd=日本") 23 print(str) 24 25 #在进行get提交时,可以使用unquote进行解码 26 str1 = urllib.parse.unquote("https%3A//www.baidu.com/s%3Fie%3Dutf-8%26wd%3D%E6%97%A5%E6%9C%AC") 27 print(str1)
1 import urllib.request 2 3 url = "http://www.baidu.com" 4 headers ={ 5 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36' 6 } 7 8 #构建高级请求对象的第一种方式 9 # req=urllib.request.Request(url=url,headers=headers) 10 ##构建高级请求对象的第二种方式-通过懒加载 11 req=urllib.request.Request(url=url) 12 req.add_header("User-Agent","Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36") 13 14 #发送请求 15 res=urllib.request.urlopen(req) 16 print(res) 17 #解码,将二进制字节转成字符串 18 print(res.read().decode("utf-8"))
1 import urllib.parse 2 import urllib.request 3 4 wd = input('请输入您要查询的词语:') 5 #编码 get请求 6 wd = urllib.parse.quote(wd) 7 url = "https://www.baidu.com/s?wd="+wd 8 # print(url) 9 # exit() 10 11 headers = { 12 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36' 13 } 14 #构建请求对象 15 req = urllib.request.Request(url=url,headers=headers) 16 #发送请求 17 res = urllib.request.urlopen(req) 18 19 #保存|写入文件 20 with open('china.html','wb') as fw: 21 fw.write(res.read()) 22 23 # with open('Japan.html','w',encoding='utf-8') as fw: 24 # fw.write(res.read().decode("utf-8"))
1 import urllib.request 2 import urllib.parse 3 from lxml import etree 4 5 #解决ssl报错 6 import ssl 7 ssl._create_default_https_context = ssl._create_unverified_context 8 9 def loadPage(url): 10 headers = {"User-Agent": "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1 Trident/5.0;"} 11 request = urllib.request.Request(url, headers = headers) 12 html = urllib.request.urlopen(request).read() 13 14 #解析HTML文档为HTML_DOM模型 15 content = etree.HTML(html) 16 #返回所有匹配成功的列表集合 17 link_list = content.xpath('//li[@class=" j_thread_list clearfix"]//div[@class="threadlist_title pull_left j_th_tit "]/a/@href') 18 print(link_list,len(link_list)) 19 20 for link in link_list: 21 fulllink = "http://tieba.baidu.com" + link #每个帖子的链接 22 loadImage(fulllink) 23 24 #取出每个帖子中每个图片的的链接 25 def loadImage(link): 26 headers = {"User-Agent": "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1 Trident/5.0;"} 27 request = urllib.request.Request(link, headers=headers) 28 html = urllib.request.urlopen(request).read() 29 content = etree.HTML(html) 30 31 #返回帖子里所有图片链接的列表集合 32 link_list = content.xpath('//img[@class="BDE_Image"]/@src') 33 for link in link_list: 34 filename = link[-15:] 35 urllib.request.urlretrieve(link,'./tieba/'+filename) 36 print("下载成功"+'----'+filename) 37 38 39 def tiebaSpider(url, beginPage, endPage): 40 41 for page in range(beginPage, endPage + 1): 42 pn = (page - 1) * 50 43 fullurl = url + "&pn=" + str(pn) 44 loadPage(fullurl) 45 46 47 if __name__ == "__main__": 48 kw = input("请输入要爬取的贴吧名:") 49 startPage = int(input("请输入起始页:")) 50 endPage = int(input("请输入结束页:")) 51 52 url = "https://tieba.baidu.com/f?" 53 54 # 可以使用urlencode({'kw':kw}) ---> https://tieba.baidu.com/f?kw=美女 55 key = urllib.parse.urlencode({"kw": kw}) 56 fullurl = url + key 57 # fullurl = url + 'kw=' +kw 58 # print(fullurl) 59 60 tiebaSpider(fullurl, startPage, endPage)
七、POST请求 和 GET请求格式
1、POST请求
import urllib.parse
import urllib.request
url = "http://fanyi.baidu.com/sug"
headers = {
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36'
}
kw =input('请输入你要查询的词:')
data = {
'kw':kw
}
#编码动作
data =urllib.parse.urlencode(data).encode('utf-8')
#构建请求对象
req=urllib.request.Request(url=url,headers=headers,data=data)
#发送请求
res=urllib.request.urlopen(req)
#读取响应数据
result=res.read().decode('utf-8')
#写入文本
with open('bdfy.json','w',encoding='utf-8') as fw:
fw.write(result)
2、GET请求
import urllib.parse
import urllib.request
url="https://movie.douban.com/top250?"
headers = {
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36'
}
page =int(input('请输入你要查看的页码:'))
start = (page-1)*25
filter= None
data ={
'start':start,
'filter':filter
}
data =urllib.parse.urlencode(data)
url = url+data
#构建请求对象
req=urllib.request.Request(url=url,headers=headers)
#发送请求
res=urllib.request.urlopen(req)
#将整个网页写入文档中
with open('douban.html','w' ,encoding='utf-8') as fw:
fw.write(res.read().decode('utf-8'))
3、例题:复杂的get请求--封装函数
1 import os 2 import urllib.parse 3 import urllib.request 4 #写数据 5 def download(req,page): 6 #发送请求 7 res=urllib.request.urlopen(req) 8 dirname ='./tieba' 9 filename ="第"+str(page)+".html" 10 filepath = os.path.join(dirname,filename) 11 with open(filepath,'wb') as fw: 12 fw.write(res.read()) 13 14 #拼接url,构建请求对象 15 def build_url(url,page,ba_name): 16 #拼接pn 17 pn =(page-1)*50 18 data ={ 19 'kw': ba_name, 20 'pn': pn 21 } 22 #编码 23 data=urllib.parse.urlencode(data) 24 url += data 25 #获取请求对象 26 req = urllib.request.Request(url=url) 27 return req 28 29 def main(): 30 start_url = int(input('请输入起始页码:')) 31 end_url = int(input('请输入结束页码:')) 32 ba_name = input('请输入贴吧名:') 33 url = 'http://tieba.baidu.com/f?ie=utf-8&' 34 for page in range(start_url,end_url+1): 35 req = build_url(url,page,ba_name) # 构建完成的url 36 download(req,page) # 根据url下载数据 37 38 if __name__ == "__main__": 39 main()
八、异常处理HTTPerror和URLerror
HTTPError类是URLError类的子类。。。通过urllib发送请求的时候,有可能会发送失败,这个时候如果想让你的代码更加的健壮,可以通过try-except进行捕获异常,异常有两类,URLError\HTTPError。
#异常处理代码
import urllib.request
import urllib.error
url = 'https://blog.csdn.net/whd526/article/details/52279108'
headers={
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36'
}
req =urllib.request.Request(url=url,headers=headers)
#在发送请求时候,进行异常处理
try:
res=urllib.request.urlopen(req)
print(res.read().decode('utf-8'))
except urllib.error.HTTPError as e: #HTTPError异常
print('httperror')
print(e)
print(e.code)
except urllib.error.URLError as e: #URLError异常
print('URLerror')
print(e)
print(e.code)
except Exception as e: #所有异常
print('Exception')
print(e)
print(e.code)
print("程序结束")
九、Handler处理器
1、简单实用(不使用代理)
1.之前的urllib.requese.urlopen() 不能构建请求头
2.urllib.request.Request(url=url, headers=headers),
高级之处可以定制请求头,但是不能携带cookie,不能使用代理。因此有Handler方法
3.使用个步骤
a、使用Handler处理器创建对象
b、使用urllib.request.bulid_opener()方法使用处理器对象。
创建自定义opener对象
c、使用自定义opener对象,调用open()方法发送请求。
4.例题
1 import urllib.request 2 3 url = "http://www.baidu.com" 4 headers = { 5 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36' 6 } 7 8 #构建一个Handler对象 9 handler=urllib.request.HTTPHandler() 10 #通过Handler对象构建一个opener对象 11 opener=urllib.request.build_opener(handler) 12 13 #构建请求对象 14 req=urllib.request.Request(url=url,headers=headers) 15 #发送请求 16 res=opener.open(req) 17 print(res.read().decode('utf-8'))
2、高级使用(使用代理)
1.代理服务器
http://www.kuaidaili.com/ 快代理(免费)
http://www.xicidaili.com/ 西刺代理(免费)
2.本地浏览器配置代理步骤[简单验证]
设置--高级--打开代理设置--链接--局域网设置--代理服务器--选中为LAN配置、跳过xxx--将代理服务器的地址和端口号写入--保存即可--百度访问ip--查看ip地址
3.代码配置代理
访问网址是http,用http代理服务器。访问https,用https代理服务器
4.例题
1 import urllib.request 2 3 url = 'https://www.baidu.com/s?ie=utf-8&wd=ip' 4 headers = { 5 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36' 6 } 7 8 #构建请求对象 9 req=urllib.request.Request(url=url,headers=headers) 10 #配置代理 11 handler=urllib.request.ProxyHandler({'https':'113.200.56.13:8010'}) 12 #构建一个opener对象 13 opener=urllib.request.build_opener(handler) 14 #发送请求 15 res=opener.open(req) 16 17 with open('ip-daili.html','wb') as fw: 18 fw.write(res.read())
十、cookielib库 和 HTTPCookieProcessor处理器
cookielib模块:主要作用是提供用于存储cookie的对象
HTTPCookieProcessor处理器:主要作用是处理这些cookie对象,并构建handler对象
1 import http.cookiejar 2 import urllib.request 3 import urllib.parse 4 5 url = 'https://passport.weibo.cn/signin/login' 6 headers ={ 7 'Host': 'passport.weibo.cn', 8 'Connection': 'keep-alive', 9 'Origin': 'https://passport.weibo.cn', 10 'Content-Type': 'application/x-www-form-urlencoded', 11 'Cookie':' _T_WM=d75ad87c2e45218a11a9193631f6aedc; login=903b2dfe558d757c50a86dbd7d018964', 12 'Referer': 'https://passport.weibo.cn/signin/login?entry=mweibo&r=http%3A%2F%2Fweibo.cn%2F&backTitle=%CE%A2%B2%A9&vt=', 13 'User-Agent':'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36', 14 } 15 data = { 16 'username':'18813145908', 17 'password':'201999gD', 18 'savestate':'1', 19 'r':'http%3A%2F%2Fweibo.cn%2F', 20 'ec':'0', 21 'entry':'mweibo', 22 'wentry':'', 23 'loginfrom':'', 24 'client_id':'', 25 'code':'', 26 'qq':'', 27 'mainpageflag':'1', 28 'vid':'cec4edb660611bdd0060e977a2c89479287a2c894792', 29 'hff':'', 30 'hfp':'' 31 } 32 33 #创建一个OOKIEJAR对象 34 cookie=http.cookiejar.CookieJar() 35 #通过HTTPCookieProcessor创建Handler对象 36 handler=urllib.request.HTTPCookieProcessor(cookie) 37 #通过Handler对象构建一个opener对象 38 opener=urllib.request.build_opener(handler) 39 #编码 40 data = urllib.parse.urlencode(data).encode('utf-8') 41 #构建请求对象 42 req=urllib.request.Request(url=url,headers=headers,data=data) 43 #发送请求 44 res=opener.open(req) 45 46 # with open('weibo.html','wb') as fw: 47 # fw.write(res.read()) 48 49 print(res.read().decode('gbk')) 50 51 url1 = 'https://weibo.cn/3386230482/info' 52 53 headers1 ={ 54 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36' 55 } 56 57 req1=urllib.request.Request(url=url1,headers=headers1) 58 res1=opener.open(req1) 59 60 print(res1.read().decode('gbk'))
十一、正则在爬虫的使用
1、正则原理
find、rfind、replace
字符串处理函数只能处理固定的字符串 baby hello
不能处理一类字符串,通过正则表达式来进行处理
正则规则:
单字符:. [abc] \d \D \w \W \s \S
数量修饰: * + ? {m} {m,} {m,n}
边界修饰 ^ $ \b(词边界) \B(非词边界)
子模式 (.*)
贪婪模式
<div>呵呵<div>哈哈</div>嘿嘿</div>
'<div>.*</div>'
懒惰匹配 .*? .+?
修饰模式
re.S 单行模式 re.M 多行模式 re.I 忽略大小写
2、例题
import re
str1 = '<div>哈哈</div><div>呵呵</div><div>嘿嘿</div>'
#贪婪模式
# res1 = re.match('<div>.*</div>',str1)
#懒惰模式
res1 = re.match('<div>.*?</div>',str1)
print(res1)
#子模式
str2 = 'goodgood678study789'
res2 = re.match('(good)(good)(\d+)study(\d+)',str2)
print(res2.group()) # group()和group(0) 结果一样,索引从1开始
print(res2.group(0)) # 这两个结果一样:goodgood678study789
print(res2.group(1)) # 结果为第一个good
print(res2.group(2)) # 结果为第二个good
print(res2.group(3)) # 结果为:678
print(res2.group(4)) # 结果为:789
#单行多行模式
# re.S 单行模式 re.M 多行模式 re.I 忽略大小写
str3 ='''good good study day dayup
good good study day dayup
nice to meet you
happy every day '''
res3 = re.compile('^good',re.S)
result3 = res3.search(str3)
print(result3)
res4 = re.compile('^good',re.M)
result4 = res4.findall(str3)
print(result4)
十二、Xpath、lxml和xml
1、XML(EXtensible Markup Language),可扩展标记语言。
##XML的节点关系
<?xml version="1.0" encoding="utf-8"?>
<bookstore>
<book>
<title>Harry Potter</title>
<author>J K. Rowling</author>
<year>2005</year>
<price>29.99</price>
</book>
</bookstore>
1.父节点:每个元素以及属性都有一个父节点。。。 book 元素是 title、author、year 以及 price 元素的父
2.子节点:元素节点可有零个、一个或多个子节点。。。title、author、year 以及 price 元素都是 book 元素的子
3.同胞:拥有相同的父节点。。。 title、author、year 以及 price 元素都是同胞
4.先辈:某节点的父、父的父。。。 title 元素的先辈是 book 元素和 bookstore 元素
5.后代:某节点的子、子的子。。。 bookstore 的后代是 book、title、author、year 以及 price 元素
2、XPath
1.基本知识
XPath (XML Path Language) 是一门在 XML 文档中查找信息的语言,可用来在 XML 文档中对元素和属性进行遍历。
W3School官方文档:http://www.w3school.com.cn/xpath/index.asp
2.选取节点
a) nodename:选取次节点中的所有子节点
bookstore #表示选取bookstore元素的所有子节点
b) /:从跟节点选取
/bookstore #选取根元素 bookstore。注释:假如路径起始于正斜杠( / ),则此路径始终代表到某元素的绝对路径!
bookstore/book #选取属于bookstore的子元素的所有book元素
c) //: 从匹配选择的当前节点选取文档中的节点,不考虑他们的位置
//book #选取所有book子元素,不管他们的位置
bookstore//book #选择属于 bookstore 元素的后代的所有 book 元素,而不管它们位于 bookstore 之下的什么位置。
//@lang #选取名为lang的所有属性
d) . : 选取当前节点
e) .. :选取当前节点的父节点
f) @ :选取属性
3.谓语(predicates)
谓语用来查找某个特定的节点或者包含某个指定的值的节点,被嵌在方括号中
###常用示例
a)bookstore/book[1] #选取属于 bookstore 子元素的第一个 book 元素。
b)bookstore/book[last()] #选取属于 bookstore 子元素的最后一个 book 元素。
c)bookstore/book[last()-1] #选取属于 bookstore 子元素的倒数第二个 book 元素。
d)bookstore/book[positon<3] #选取最前面的两个属于 bookstore 元素的子元素的 book 元素。
e)//title[@lang] #选取所有拥有名为 lang 的属性的 title 元素。
f)//title[@lang=’eng’] #选取所有 title 元素,且这些元素拥有值为 eng 的 lang 属性。
g)/bookstore/book[price>35.00] #选取 bookstore 元素的所有 book 元素,且其中的 price 元素的值须大于 35.00。
h)/bookstore/book[price>35.00]/title #选取 bookstore 元素中的 book 元素的所有 title 元素,且其中的 price 元素的值须大于 35.00。
4.选取未知节点
a) * :匹配任何元素节点
/bookstore/* #选取 bookstore 元素的所有子元素。
b) @*:匹配任何属性节点
//* #选取文档中的所有元素。
c) node:匹配任何类型的节点
//title[@*] #选取所有带有属性的 title 元素
5.选取若干路径
通过在路径表达式中使用“|”运算符,您可以选取若干个路径
//book/title | //book/price #选取 book 元素的所有 title 和 price 元素。
//title | //price #选取文档中的所有 title 和 price 元素。
/bookstore/book/title | //price #选取属于 bookstore 元素的 book 元素的所有 title 元素,以及文档中所有的 price 元素。
6.其他获取节点方法
属性定位:根据属性查找标签
层级定位:一级一级查找
索引定位:下标从1开始
查找id是maincontent的div下面的h1节点
//div[@id="maincontent"]/h1
//div[@class="head_wrapper"]/div[@id="u"]/a[1]
逻辑运算 //div[@id="head" and @class="s_down"]
模糊匹配
查找所有的div,id中有he的div
//div[contains(@id, "he")]
查找所有的div,id中以he开头的div
//div[starts-with(@id, "he")]
查找所有的div,id中以he结尾的div
//div[ends-with(@id, "he")]
取文本
//div[@class="head_wrapper"]/div[@id="u"]/a[1]/text()
//div[@class="head_wrapper"]/div[@id="u"]/a[1]
obj.text 将内容获取到
取属性
//div[@class="head_wrapper"]/div[@id="u"]/a[1]/@href
7.程序中使用xpath方法
from lxml import etree
d_etree = etree.parse('本地html')
d_etree = etree.HTML('网上html字符串(也可以是字节类型)')
d_etree.xpath('xpath路径') 返回的是一个列表
获取到节点对象之后obj obj.xpath('xpath路径')
1 #文档内容 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8" /> 5 <title>xpath测试页面</title> 6 </head> 7 <body> 8 <ol> 9 <li class="haha">醉卧沙场君莫笑,古来征战几人回</li> 10 <li class="heihei">两岸猿声啼不住,轻舟已过万重山</li> 11 <li id="hehe" class="nene">一骑红尘妃子笑,无人知是荔枝来</li> 12 <li class="xixi">停车坐爱枫林晚,霜叶红于二月花</li> 13 <li class="lala">商女不知亡国恨,隔江犹唱后庭花</li> 14 </ol> 15 <div id="pp"> 16 <div> 17 <a href="http://www.baidu.com">李白</a> 18 </div> 19 <ol> 20 <li class="huanghe">君不见黄河之水天上来,奔流到海不复回</li> 21 <li id="tata" class="hehe">李白乘舟将欲行,忽闻岸上踏歌声</li> 22 <li class="tanshui">桃花潭水深千尺,不及汪伦送我情</li> 23 </ol> 24 <div class="hh"> 25 <a href="http://mi.com">雷军</a> 26 </div> 27 <ol> 28 <li class="dudu">nice to meet you</li> 29 <li class="meme">hao do you do</li> 30 </ol> 31 </div> 32 </body> 33 </html>
1 from lxml import etree 2 #构建document对象 3 e_tree=etree.parse('testXpath.html') 4 # print(e_tree) 5 #1-获取所有的li节点 6 res=e_tree.xpath('//li') 7 8 #2-获取所有li节点的class属性 9 res=e_tree.xpath('//li/@class') 10 11 #3-获取每一个ol节点最后一个li节点的文本内容 12 res=e_tree.xpath('//ol/li[last()]/text()') 13 14 #4-拿到http://mi.com值 15 res=e_tree.xpath('//div[@class="hh"]/a/@href')[0] 16 17 #5-拿到雷军文本值 18 res=e_tree.xpath('//div[@class="hh"]/a/text()')[0] 19 20 #6-找到第2个ol节点里面class以h开头的li节点 21 res=e_tree.xpath('//div[@id="pp"]/ol/li[starts-with(@class,"h")]/text()') 22 23 #7-找到第2个ol节点里面class以h开头的第2个li节点文本 24 res=e_tree.xpath('//div[@id="pp"]/ol/li[starts-with(@class,"h")]/text()')[1] 25 print(res)
1 import os 2 import urllib.request 3 from lxml import etree 4 5 def download_img(url_list, name_list): 6 print('进入download') 7 dirpath = "./imgs" 8 for i in range(len(name_list)): 9 #获取图片后缀名 10 suffix = os.path.splitext(url_list[i]) 11 print(suffix) 12 suffix = suffix[-1] 13 14 #拼接图片全路径 15 file_path = os.path.join(dirpath,name_list[i])+suffix 16 try: 17 urllib.request.urlretrieve(url_list[i],file_path) 18 print("%s-下载成功" % file_path) 19 except Exception as e: 20 print("%s-下载失败" % file_path) 21 22 def get_data(req): 23 print('进入get—data') 24 #发送请求 25 res = urllib.request.urlopen(req) 26 #读取响应内容,获取整个网页内容 27 html = res.read().decode('utf-8') 28 #构建document对象 29 doc_etree = etree.HTML(html) 30 #找到所有图片的url 31 url_list = doc_etree.xpath('//div[@id="container"]/div/div/a/img/@src2') #最好查看源码获取src还是src2 32 name_list = doc_etree.xpath('//div[@id="container"]/div/div/a/img/@alt') 33 34 #下载图片 35 download_img(url_list,name_list) 36 37 def bulid_req(url): 38 print("进入bulid_req") 39 headers = { 40 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36' 41 } 42 req = urllib.request.Request(url=url,headers=headers) 43 return req 44 45 def main(): 46 start_page = int(input('开始页码:')) 47 end_page = int(input('结束页码:')) 48 url_temp = 'http://sc.chinaz.com/tupian/meinvtupian' 49 print('正在给你下载啊。。。骚年') 50 for page in range(start_page,end_page+1): 51 if page != 1: 52 url = url_temp + "_" + str(page) + ".html" 53 else: 54 url = url_temp 55 print(url) 56 #构建一个请求对象 57 req = bulid_req(url) 58 get_data(req) 59 60 if __name__ == "__main__": 61 main()
1 #糗事百科实战 2 import json 3 import urllib.request 4 from lxml import etree 5 6 def build_url(url): 7 headers={ 8 'User-Agent':'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36' 9 } 10 req=urllib.request.Request(url=url,headers=headers) 11 return req 12 13 def get_data(req): 14 res=urllib.request.urlopen(req) 15 doc = res.read().decode('utf-8') 16 doc_html =etree.HTML(doc) 17 div_list=doc_html.xpath('//div[@id="content-left"]//div[starts-with(@class,"author")]') 18 # print(len(div_list)) 19 # print(div_list) 20 # print(type(div_list)) 21 items = [] 22 for div in div_list: 23 item = {} 24 img_temp_url = div.xpath('./a/img/@src')[0] 25 img_url ='https:'+img_temp_url 26 name_tmp =div.xpath('./a/h2/text()')[0] 27 name = name_tmp.strip('\n') 28 # print(name) 29 # exit() 30 item['img_url'] = img_url 31 item['name'] = name 32 items.append(item) 33 str=json.dumps(items,ensure_ascii=False) 34 with open('qiushi.json','w',encoding='utf-8') as fw: 35 fw.write(str) 36 37 def main(): 38 url = 'https://www.qiushibaike.com/text/' 39 req=build_url(url) 40 get_data(req) 41 42 if __name__ == "__main__": 43 main()
十三、bs4(BeautifulSoup4)
1、bs4解析器
1.概述:
lxml 只会局部遍历,而Beautiful Soup 是基于HTML DOM的,会载入整个文档,解析整个DOM树,
因此时间和内存开销都会大很多,所以性能要低于lxml。
BeautifulSoup 用来解析 HTML 比较简单,API非常人性化,
支持CSS选择器、Python标准库中的HTML解析器,也支持 lxml 的 XML解析器。
2.使用方法
from bs4 import BeautifulSoup
# 本地文件生成对象
soup = BeautifulSoup(open('bs4_test.html'), 'lxml')
# 网上文件生成对象
soup = BeautifulSoup('网上下载的字符串', 'lxml')
2、四大对象种类
Beautiful Soup将复杂HTML文档转换成一个复杂的树形结构,每个节点都是Python对象,
所有对象可以归纳为:Tag、NavigableString、BeautifulSoup、Comment
1.Tag通俗点讲就是HTML中的每一个标签
例如: title、head、a、p等等 HTML 标签加上里面包括的内容就是 Tag
<head><title>The Dormouse's story</title></head>
<a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>
<p class="title" name="dromouse"><b>The Dormouse's story</b></p>
例子:
html = """
<html><head><title>The Dormouse's story</title></head>
<body>
<p class="title" name="dromouse"><b>The Dormouse's story</b></p>
<p class="story">Once upon a time there were three little sisters; and their names were
<a href="http://example.com/elsie" class="sister" id="link1"><!-- Elsie --></a>,
<a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and
<a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>;
and they lived at the bottom of a well.
</p>
<p class="story">...</p>
"""
from bs4 import BeautifulSoup
#创建 Beautiful Soup 对象
soup = BeautifulSoup(html)
print(soup.title) #获取title的tag # <title>The Dormouse's story</title>
print(soup.head) #获取head的tag # <head><title>The Dormouse's story</title></head>
print(soup.a) #获取a的tag # <a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>
print(soup.p) #获取p的tag # <p class="title" name="dromouse"><b>The Dormouse's story</b></p>
我们可以利用 soup 加标签名轻松地获取这些标签的内容,这些对象的类型是bs4.element.Tag。
但是注意,它查找的是在所有内容中的第一个符合要求的标签
# 对于Tag有两个重要属性,name和attrs
print(soup.name) #获取标签名
# [document] #soup 对象本身比较特殊,它的 name 即为 [document]
print(soup.head.name)
# head #对于其他内部标签,输出的值便为标签本身的名称
print(soup.p.attrs) #获取p标签的attrs,将属性作为字典返回
# {'class': 'title', 'name': 'dromouse'}
# 获取节点属性值的3中方法(字典取值):
obj.attrs.get('title') obj.get('title') obj['title']
print(soup.p['class']) #等效于 print(soup.p.get('class'))
# ['title'] #还可以利用get方法,传入属性的名称,二者是等价的
soup.p['class'] = "newClass"
print(soup.p) # 可以对这些属性和内容等等进行修改
# <p class="newClass" name="dromouse"><b>The Dormouse's story</b></p>
del soup.p['class'] # 还可以对这个属性进行删除
print(soup.p)
# <p name="dromouse"><b>The Dormouse's story</b></p>
2. NavigableString(获取标签内容)
obj.string 和 obj.get_text()
# 如果内容中有注释,string会获取到注释的内容,get_text()获取不到注释内容,但可以获取非注释内容
# 如果内容中有标签,string返回None,get_text()返回文本内容。
print(soup.p.string) # The Dormouse's story
3. BeautifulSoup 对象表示的是一个文档的内容。大部分时候,可以把它当作 Tag 对象,是一个特殊的 Tag
4. Comment 对象是一个特殊类型的 NavigableString 对象,其输出的内容不包括注释符号。
print(soup.a)
# <a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>
print(soup.a.string)
# Elsie
3、遍历文档树
1.直接子节点 :.contents .children 属性
.contents:可以将tag的子节点以列表的方式输出
print(soup.head.contents) #[<title>The Dormouse's story</title>]
print(soup.head.contents[0]) #<title>The Dormouse's story</title>
.childern: 它返回的不是列表而是一个list生成器,可以通过遍历获取所有子节点。
print(soup.head.children) #<listiterator object at 0x7f71457f5710>
for child in soup.body.children:
print(child) #输出的内容为body标签中的所有标签
2.所有的子孙节点:.descendants 属性
.contents 和 .children 属性仅包含tag的直接子节点,
.descendants 属性可以对所有tag的子孙节点进行递归循环,和 children类似,我们也需要遍历获取其中的内容。
for child in soup.descendants:
print(child) #输出结果为所有的html中的标签。并且递归循环,一层一层进行输出
4、搜索文档树
1.find #返回第一个对象
find('a') #返回第一个a标签
find('a', title='xxx') #返回第一个a标签,并且其title='xxx'
find('a', class_='xxx') #返回第一个a标签,并且其class='xxx'
2.find_all(name, attrs, recursive, text, **kwargs)
1)传字符串
print(soup.find_all('b')) #获取所有的b标签
# [<b>The Dormouse's story</b>]
print(soup.find_all('a')) #获取所有a标签
# [<a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>,
# <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]]
2)传正则表达式 (通过正则表达式的 match() 来匹配内容)
#获取所有以b开头的标签
import re
for tag in soup.find_all(re.compile("^b")):
print(tag.name)
# body
# b
3)传列表 (获取列表中的每个标签)
#获取a标签和b标签
print(soup.find_all(["a", "b"]))
# [<b>The Dormouse's story</b>,
# <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
# <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
print(soup.find_all(['a',limit=2]))
#获取所有a标签中的前两个
4)通过id获取
print(soup.find_all(id='link2')) #获取id='link2'的标签,并以列表返回
# [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]
5)通过text获取
通过 text 参数可以搜搜文档中的字符串内容,与 name 参数的可选值一样, text 参数接受 字符串 , 正则表达式 , 列表
soup.find_all(text="Elsie")
# [u'Elsie']
soup.find_all(text=["Tillie", "Elsie", "Lacie"])
# [u'Elsie', u'Lacie', u'Tillie']
soup.find_all(text=re.compile("Dormouse"))
[u"The Dormouse's story", u"The Dormouse's story"]
3.通过选择器获取
a、标签名不加任何修饰,类名前加. id名前加#
b、在这里我们也可以利用类似的方法来筛选元素,用到的方法是 soup.select(),返回类型是 list
1)通过标签名查找
print(soup.select('title'))
#[<title>The Dormouse's story</title>]
2)通过类名查找
print(soup.select('.sister'))
#[<a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>,
# <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
3)通过id查找
print(soup.select('#link1'))
#[<a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>]
4)组合查找
组合查找即和写 class 文件时,标签名与类名、id名进行的组合原理是一样的,例如查找 p 标签中,id 等于 link1的内容,二者需要用空格分开。
标签名 .类名 #id名 .类名
标签名 > .类名 > #id名
print(soup.select('p #link1')) #p标签 的子孙中 id=link1的
#[<a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>]
print(soup.select("head > title")) #head标签下的title标签
#[<title>The Dormouse's story</title>]
5)属性查找
查找时还可以加入属性元素,属性需要用中括号括起来,注意属性和标签属于同一节点,所以中间不能加空格,否则会无法匹配到。
print(soup.select('a[class="sister"]'))
#[<a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>, <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>, <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
print(soup.select('a[href="http://example.com/elsie"]'))
#[<a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>]
同样,属性仍然可以与上述查找方式组合,不在同一节点的空格隔开,同一节点的不加空格
print(soup.select('p a[href="http://example.com/elsie"]'))
#[<a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>]
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>soup测试</title> 6 </head> 7 <body> 8 <div class="tang"> 9 <ul> 10 <li><a href="http://www.baidu.com" title="出塞">秦时明月汉时关,万里长征人未还,但使龙城飞将在,不教胡马度阴山</a></li> 11 <li><a href="http://www.163.com" class="taohua">人面不知何处去,桃花依旧笑春风</a></li> 12 <li><a href="http://mi.com" id="hong">去年今日此门中,人面桃花相映红</a></li> 13 <li><a href="http://qq.com" class="taohua">故人西辞黄鹤楼,烟花三月下扬州</a></li> 14 </ul> 15 </div> 16 <div id="meng"> 17 <p class="jiang"> 18 <span>三国猛将</span> 19 <ol> 20 <li>关羽</li> 21 <li>张飞</li> 22 <li>赵云</li> 23 <li>马超</li> 24 <li>黄忠</li> 25 </ol> 26 <div class="cao"> 27 <ul> 28 <li>典韦</li> 29 <li>许褚</li> 30 <li>张辽</li> 31 <li>张郃</li> 32 <li>于禁</li> 33 <li>夏侯惇</li> 34 </ul> 35 </div> 36 </p> 37 </div> 38 </body> 39 </html>
1 import bs4 2 from bs4 import BeautifulSoup 3 4 # 创建bs4对象 5 bs=BeautifulSoup(open('1-bs4_test.html'),'lxml') 6 # 找到a标签和title 7 tag_a=bs.a 8 print(tag_a) 9 print(type(tag_a)) 10 print(bs.title) 11 # 点属性attrs 12 print(tag_a.attrs) 13 print(tag_a.name) 14 # 获取节点属性值(3种方式) 15 print(tag_a.attrs.get("title")) 16 print(tag_a.get("title")) 17 print(tag_a['title']) 18 # 获取节点内容 19 print(tag_a.string) 20 print('-----------------------') 21 print(tag_a.get_text()) 22 # 获取直接子节点 23 print(len(bs.body.contents)) 24 # 获取子孙节点 25 print(bs.body.descendants) 26 for tag in bs.body.descendants: 27 print(tag) 28 find find_all select 29 print(bs.find('a')) 30 print(bs.find('a',title="出塞")) 31 print(bs.find('a',id="hong")) 32 print(bs.find('a',class_="taohua")) 33 print(bs.find('a',href="http://qq.com")) 34 35 print(bs.find_all('a')) 36 print(bs.find_all('a',limit=2)) 37 print(bs.find_all(['a','span'])) 38 39 # 根据层级选择器 和属性选择器 40 print(bs.select('div[class="tang"] a[class="taohua"]')) 41 # 获取最后一个内容 42 print(bs.select('div[class="tang"] a[class="taohua"]')[-1].string)
1 import json 2 import urllib.parse 3 import urllib.request 4 import bs4 5 from bs4 import BeautifulSoup 6 7 8 class QianChen(object): 9 def __init__(self,url,gzgw,start_page,end_page): 10 super(QianChen,self).__init__() 11 self.url = url 12 self.gzgw = gzgw 13 self.start_page = start_page 14 self.end_page = end_page 15 self.headers={ 16 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36' 17 } 18 self.items = [] 19 20 #构建请求对象 21 def build_url(self,page): 22 data ={ 23 'gzgw':self.gzgw, 24 'page' :str(page) 25 } 26 url = self.url+urllib.parse.quote(data['gzgw'])+',2,'+data['page']+'.html' 27 req = urllib.request.Request(url=url,headers=self.headers) 28 return req 29 30 #解析数据 31 def download(self,req): 32 res =urllib.request.urlopen(req) 33 bs = BeautifulSoup(res.read(),'lxml') 34 div_list=bs.select('#resultList .el')[1:] 35 # print(len(div_list)) 36 for div in div_list: 37 item = {} 38 gzgw =div.select('p a')[0].get_text() 39 gsmc = div.select('.t2 > a')[0].get_text() 40 gzdd = div.select('.t3')[0].get_text() 41 zwxs = div.select('.t4')[0].get_text() 42 fbrq = div.select('.t5')[0].get_text() 43 item['gzgw'] = gzgw.strip() 44 item['gsmc'] = gsmc 45 item['gzdd'] = gzdd 46 item['zwxs'] = zwxs 47 item['fbrq'] = fbrq 48 self.items.append(item) 49 50 #对外提供的接口函数 51 def start(self): 52 for page in range(self.start_page,self.end_page+1): 53 req=self.build_url(page) 54 self.download(req) 55 56 #写入文件 57 str = json.dumps(self.items,ensure_ascii=False) 58 with open('qcwy.json','w',encoding='utf-8') as fw: 59 fw.write(str) 60 61 def main(): 62 url = "https://search.51job.com/list/010000,000000,0000,00,9,99," 63 gzgw=input('请输入职位名称:') 64 start_page = int(input('请输入开始页码:')) 65 end_page = int(input('请输入结束页码:')) 66 qc = QianChen(url,gzgw,start_page,end_page) 67 qc.start() 68 69 if __name__ == "__main__": 70 main()
#######后续更新

浙公网安备 33010602011771号