Python爬虫进阶——Xpath解析数据 并 爬取一个IP池

前言:

老早以前写过一个用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

感谢w3school运算符

 


插曲:

基本语法就先记录到这里了,感谢这位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 parselpython -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池就搭建完成

 


 

 

 

 

 

商业转载 请联系作者获得授权,非商业转载 请标明出处,谢谢

发表评论