前言:
老早以前写过一个用python的request包的爬虫文章,那里面我使用正则表达式去对信息进行筛选,今天我们来学习一种更简便的解析数据、筛选信息的方法,利用Xpath,并且了解了基本语法后,我们来爬取一个IP池。
Xpath:
XPath,全称 XML Path Language,即 XML 路径语言,它是一门在XML文档中查找信息的语言。XPath 最初设计是用来搜寻XML文档的,但是它同样适用于 HTML 文档的搜索。它也是是 W3C XSLT 标准的主要元素,并且 XQuery 和 XPointer 都构建于 XPath 表达之上。因此,对 XPath 的理解是很多高级 XML 应用的基础。
一些术语(可以参考XML的知识方便理解):
1. 在 XPath 中,有七种类型的节点:元素、属性、文本、命名空间、处理指令、注释以及文档节点(或称为根节点)。XML 文档是被作为节点树来对待的。树的根被称为文档节点或者根节点。
2. 基本值(或称原子值,Atomic value)是无父或无子的节点。
3. 项目(Item)是基本值或者节点。
基本语法:
表达式 | 描述 |
nodename | 选择这个节点名的所有子节点 |
/ | 从当前节点选择直接子节点 |
// | 从当前节点选取子孙节点 |
. | 选择当前节点 |
… | 选取当前节点的父节点 |
@ | 选取属性 |
实例:
表达式 | 结果 |
node | 选取 node元素的所有子节点。 |
/node | 选取根元素 node。假如路径起始于 / ,则此路径始终代表到某元素的绝对路径,就像Linux里的路径规则。 |
node/bit | 选取属于 node 的子元素的所有 bit 元素。 |
//node | 选取所有 node 子元素,而不管它们在文档中的位置。 |
node//bit | 选择属于 node 元素的后代的所有 bit 元素,而不管它们位于 node 之下的什么位置。 |
//@lang | 选取名为 lang 的所有属性。 |
谓语(Predicates)
谓语用来查找某个特定的节点或者包含某个指定的值的节点。
谓语被嵌在方括号中。
表达式 | 结果 |
/bookstore/book[1] | 选取属于 bookstore 子元素的第一个 book 元素。 |
/bookstore/book[last()] | 选取属于 bookstore 子元素的最后一个 book 元素。 |
/bookstore/book[last()-1] | 选取属于 bookstore 子元素的倒数第二个 book 元素。 |
/bookstore/book[position()<3] | 选取最前面的两个属于 bookstore 元素的子元素的 book 元素。 |
//title[@lang] | 选取所有拥有名为 lang 的属性的 title 元素。 |
//title[@lang=’eng’] | 选取所有 title 元素,且这些元素拥有值为 eng 的 lang 属性。 |
/bookstore/book[price>35.00] | 选取 bookstore 元素的所有 book 元素,且其中的 price 元素的值须大于 35.00。 |
/bookstore/book[price>35.00]/title | 选取 bookstore 元素中的 book 元素的所有 title 元素,且其中的 price 元素的值须大于 35.00。 |
这里感谢这位兄台总结的表——https://cuiqingcai.com/2621.html
上手操作:
标签补全:
首先我们在Pycharm的终端运行命令来安装lxml,这里为了学习Xpath先安装lxml
pip3 install lxml
我们可以先来简单的小试牛刀
#在你的模块里导入指定的模块属性
from lxml import html
etree=html.etree
text='''
<html>
<head>
<title>ISHASH.COM</title>
</head>
<body>
<div>
<ul>
<li class="item-0"><a href="https://is-hash.com">first item</a></li>
</ul>
</div>
'''
html=etree.HTML(text)
result=etree.tostring(html)
print(result.decode('UTF-8'))
输出:
<html>
<head>
<title>ISHASH.COM</title>
</head>
<body>
<div>
<ul>
<li class="item-0"><a href="https://is-hash.com">first item</a></li>
</ul>
</div>
</body></html>
注解:
上面的 etree.HTML() 是将字符串解析为html文档并对HTML文本进行自动修正。
而后面的 etree.tostring() 输出修正后的结果,类型是bytes。
另外上面的道理读取html文件也是可以的:
#在你的模块里导入指定的模块属性
from lxml import html
etree=html.etree
html=etree.parse('./test.html',etree.HTMLParser())
result=etree.tostring(html)
print(result.decode('UTF-8'))
获取所有节点:
//test.html
<!DOCTYPE html>
<html>
<head>
<title>ISHASH.COM</title>
</head>
<div>
<ul>
<li class="item-0"><a href="https://is-hash.com">first item</a></li>
<li class="item-1"><a href="https://baidu.com">second item</a></li>
</ul>
</div>
<table>
<tbody>
<tr>
<td>Look at me!</td>
<td>Or kill me!</td>
</tr>
</tbody>
</table>
</body></html>
//main.py
#在你的模块里导入指定的模块属性
from lxml import html
etree=html.etree
html=etree.parse('./test.html',etree.HTMLParser())
#'//'表示获取当前节点的子孙节点,'*'表示通配符
#合起来则是获取当前节点的所有节点,返回值是个列表
result=html.xpath('//*')
for item in result:
print(item)
//输出
<Element html at 0x2892284b748>
<Element head at 0x2892285e9c8>
<Element title at 0x2892285ef88>
<Element body at 0x2892286a108>
<Element div at 0x28922b9ba48>
<Element ul at 0x28922b9bac8>
<Element li at 0x28922b9bb08>
<Element a at 0x28922b9bb48>
<Element li at 0x28922b9bb88>
<Element a at 0x28922b9ba88>
<Element table at 0x28922b9bbc8>
<Element tbody at 0x28922b9bc08>
<Element tr at 0x28922b9bc48>
<Element td at 0x28922b9bc88>
<Element td at 0x28922b9bcc8>
如果想要获得所有tbody的节点,只要修改上面的xpath即可:
result=html.xpath('//tbody')
获取所有子节点
还是在上面程序的基础上进行修改,修改xpath里的匹配规则
比如我想要获得tbody节点下的tr的直接子节点
result=html.xpath('//tbody/tr')
如果是所有子孙的tr节点:
result=html.xpath('//tbody//tr')
根据属性获取
结合上面说的,加一个谓语即可
result=html.xpath('//li[@class="item-0"]')
如上所示,获取所有 li节点 且要求具有属性 class=”item-0″
获取父节点
利用 .. 即可往上翻一层
例如获取tr的父节点
result=html.xpath('//tr/..')
也可以用 节点轴——parent::* 来获取父节点
result=html.xpath('//tr/parent::*')
◊获取文本信息
其实更常用的操作还是获取文本信息
我们用 XPath 中的 text() 方法可以获取节点中的文本
例如上面的html文件中,有两个td节点,里面分别有一些字符串,我们现在来获取他们
result=html.xpath('//td/text()')
输出:
Look at me!
Or kill me!
另外,有的时候我们可以直接用 // 加 text() 的方式获取,这样可以爬到最全面的文本信息,但是可能会夹杂一些换行符等特殊字符。所以还是定位的越细致越好。
属性获取
获取li节点的属性
result=html.xpath('//li/@class')
属性多值获取
例如上面的html文件中有这样的一句
<li class="sp item-0"><a href="https://is-hash.com">first item</a></li>
我们要经过 li 节点去获取内部的 first item 文本
这时就可以用到 contains() 函数了
result=html.xpath('//li[contains(@class,"sp")]//text()')
这样只要li节点还有属性 class,且属性有值sp即可被匹配
多属性获取
当一个节点有多个属性
<li class="sp item-0" name="item"><a href="https://is-hash.com">first item</a></li>
利用Xpath的运算符即可:
result=html.xpath('//li[contains(@class,"sp") and @name="item"]//text()')
下面是Xpath的常见运算符
运算符 | 描述 | 实例 | 返回值 |
---|---|---|---|
| | 计算两个节点集 | //book | //cd | 返回所有拥有 book 和 cd 元素的节点集 |
+ | 加法 | 6 + 4 | 10 |
– | 减法 | 6 – 4 | 2 |
* | 乘法 | 6 * 4 | 24 |
div | 除法 | 8 div 4 | 2 |
= | 等于 | price=9.80 | 如果 price 是 9.80,则返回 true。
如果 price 是 9.90,则返回 false。 |
!= | 不等于 | price!=9.80 | 如果 price 是 9.90,则返回 true。
如果 price 是 9.80,则返回 false。 |
< | 小于 | price<9.80 | 如果 price 是 9.00,则返回 true。
如果 price 是 9.90,则返回 false。 |
<= | 小于或等于 | price<=9.80 | 如果 price 是 9.00,则返回 true。
如果 price 是 9.90,则返回 false。 |
> | 大于 | price>9.80 | 如果 price 是 9.90,则返回 true。
如果 price 是 9.80,则返回 false。 |
>= | 大于或等于 | price>=9.80 | 如果 price 是 9.90,则返回 true。
如果 price 是 9.70,则返回 false。 |
or | 或 | price=9.80 or price=9.70 | 如果 price 是 9.80,则返回 true。
如果 price 是 9.50,则返回 false。 |
and | 与 | price>9.00 and price<9.90 | 如果 price 是 9.80,则返回 true。
如果 price 是 8.50,则返回 false。 |
mod | 计算除法的余数 | 5 mod 2 | 1 |
插曲:
基本语法就先记录到这里了,感谢这位CSDN的带翅膀的猫
另外这个谷歌插件也是很不错——XPath Helper
按住shift选择我们需要的内容,自动生成匹配规则
或者更简单的,在审查元素里找到你要的资源右击,选择Copy,就可以直接copy这个节点的xpath
爬取IP池:
下面的例子仅供参考
我们找到一个免费ip代理网站,这个其实还蛮好找的
我们先来确定网页是静态网页还是动态:如果是静态,那网页源代码里的数据就是呈现给你的数据,即网页的任何数据都是在源代码中可以找到的,它的url地址对应的就是导航栏中的地址。
我们这个网站是个静态网站,好,我直接把url记录到python代码中
在网页中我们在审查元素的network里(刷新一下)得到UA头,来伪造我们的爬虫是浏览器
import request;
base_url="https://www.马赛克.com/free/inha/1/";
headers={'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36'}
接下来我们使用requests模块的get去发送请求
response=requests.get(base_url,headers=headers)
#接收数据
data=response.text
data.encode("UTF-8")
print(data)
一般没问题的就会发现它成功把网页源代码打印了出来
好,现在咱们就要利用Xpath去解析数据,把需要的数据筛出来,首先我们先安装parsel库
pip install parsel
or easy_install parsel
(python -m pip install --upgrade
)
然后在代码中导入
import parsel
对于该网站的html中,我们需要的数据就是 IP、IP端口、协议类型 ,这三个字段
最后我们要弄成这样{“协议类型”:”IP:IP端口”}
每一个IP信息在审查元素中都是这样:
故,利用这样的Xpath即可得到每个tr标签内的信息
#将data数据转换成一个selector对象
html_data=parsel.Selector(data)
XpathSelect='//table[@class="table table-bordered table-striped"]/tbody/tr'
parse_list=html_data.xpath(XpathSelect)
此时,parse_list就是一个列表,我们再用for循环遍历,将每一个代理IP作为一个字典存到一个集合中:
#一个空列表用来存储所有的代理IP
proxies_list=[]
for tr in parse_list:
#代理IP的形式是一个类字典
proxies_dict={}
#提取协议类型
http_type=tr.xpath("./td[4]/text()").extract_first()
IP = tr.xpath("./td[1]/text()").extract_first()
IP_Port = tr.xpath("./td[2]/text()").extract_first()
#将数据加入字典
proxies_dict[http_type]=IP+":"+IP_Port
proxies_list.append(proxies_dict)
好了,这一页的代理IP就全被我们采集了:
import requests
import parsel
base_url="https://www.马赛克.com/free/inha/1/";
headers={'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36'}
response=requests.get(base_url,headers=headers)
#接收数据
data=response.text
data.encode("UTF-8")
#解析数据
#将data数据转换成一个selector对象
html_data=parsel.Selector(data)
XpathSelect='//table[@class="table table-bordered table-striped"]/tbody/tr'
parse_list=html_data.xpath(XpathSelect)
#一个空列表用来存储所有的代理IP
proxies_list=[]
for tr in parse_list:
#代理IP的形式是一个类字典
proxies_dict={}
#提取协议类型
http_type=tr.xpath("./td[4]/text()").extract_first()
IP = tr.xpath("./td[1]/text()").extract_first()
IP_Port = tr.xpath("./td[2]/text()").extract_first()
#将数据加入字典
proxies_dict[http_type]=IP+":"+IP_Port
proxies_list.append(proxies_dict)
print(proxies_list)
print("获取到的代理IP数量是",len(proxies_list),"个")
那要采集多页呢?我们调动数据之后发现不同的页的区别其实就是在URL中的最后数字的变化,好,我们再做个大循环,将刚才的数据做到循环里面:
import requests
import parsel
import time
# 一个空列表用来存储所有的代理IP
proxies_list = []
for page in range(1,5):
#用{}预留一个接口,通过.format将页数进行传递
base_url="https://www.马赛克.com/free/inha/{}/".format(page)
headers={'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36'}
response=requests.get(base_url,headers=headers)
#接收数据
data=response.text
data.encode("UTF-8")
#解析数据
#将data数据转换成一个selector对象
html_data=parsel.Selector(data)
XpathSelect='//table[@class="table table-bordered table-striped"]/tbody/tr'
parse_list=html_data.xpath(XpathSelect)
for tr in parse_list:
#代理IP的形式是一个类字典
proxies_dict={}
#提取协议类型
http_type=tr.xpath("./td[4]/text()").extract_first()
IP = tr.xpath("./td[1]/text()").extract_first()
IP_Port = tr.xpath("./td[2]/text()").extract_first()
#将数据加入字典
proxies_dict[http_type]=IP+":"+IP_Port
proxies_list.append(proxies_dict)
print(proxies_dict)
time.sleep(0.5)
print("获取到的代理IP数量是",len(proxies_list),"个")
最后输出:
......
......
{'HTTP': '58.253.153.221:9999'}
{'HTTP': '122.192.39.239:8118'}
{'HTTP': '115.218.215.101:9000'}
{'HTTP': '183.166.20.211:9999'}
{'HTTP': '163.204.244.7:9999'}
{'HTTP': '163.204.240.10:9999'}
{'HTTP': '117.95.232.210:9999'}
获取到的代理IP数量是 60 个
好,接下来我们来对 proxies_list 进行筛选高质量的IP
再定义一个方法:
#定义一个检测爬取到的IP的质量的方法
def check_IP(proxies_list):
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36'}
#定义一个列表来选出高质量的ip
can_use=[]
#使用代理IP访问服务器从而检查IP质量
for proxy in proxies_list:
try:
#使用代理IP访问某站并要求0.1秒内给出响应(若超过0.1秒则会异常报错)
response=requests.get('https://baidu.com',headers=headers,proxies=proxy,timeout=0.1)
#高质量的代理IP
if response.status_code==200:
can_use.append(proxy)
except Exception as e:
print(e)
return can_use
最后全部的代码如下:
import requests
import parsel
import time
#定义一个检测爬取到的IP的质量的方法
def check_IP(proxies_list):
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36'}
#定义一个列表来选出高质量的ip
can_use=[]
#使用代理IP访问服务器从而检查IP质量
for proxy in proxies_list:
try:
#使用代理IP访问某站并要求0.1秒内给出响应(若超过0.1秒则会异常报错)
response=requests.get('https://baidu.com',headers=headers,proxies=proxy,timeout=0.1)
#高质量的代理IP
if response.status_code==200:
can_use.append(proxy)
except Exception as e:
print(e)
return can_use
# 一个空列表用来存储所有的代理IP
proxies_list = []
for page in range(1,5):
#用{}预留一个接口,通过.format将页数进行传递
base_url="https://www.马赛克.com/free/inha/{}/".format(page)
headers={'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36'}
response=requests.get(base_url,headers=headers)
#接收数据
data=response.text
data.encode("UTF-8")
#解析数据
#将data数据转换成一个selector对象
html_data=parsel.Selector(data)
XpathSelect='//table[@class="table table-bordered table-striped"]/tbody/tr'
parse_list=html_data.xpath(XpathSelect)
for tr in parse_list:
#代理IP的形式是一个类字典
proxies_dict={}
#提取协议类型
http_type=tr.xpath("./td[4]/text()").extract_first()
IP = tr.xpath("./td[1]/text()").extract_first()
IP_Port = tr.xpath("./td[2]/text()").extract_first()
#将数据加入字典
proxies_dict[http_type]=IP+":"+IP_Port
proxies_list.append(proxies_dict)
print(proxies_dict)
time.sleep(0.5)
print("获取到的代理IP数量是",len(proxies_list),"个")
#检测代理IP可用性
can_use=check_IP(proxies_list)
print("能用的IP数量",len(can_use))
不错吧,一共60个IP,56个质量不错,
爬取到的数据可以存放到数据库中,例如MongoDB数据库(非关系型数据库,无需建字段)
好了,IP池就搭建完成
博客:is-hash.com
商业转载 请联系作者获得授权,非商业转载 请标明出处,谢谢