第三十二章:爬虫

一:爬虫

1.爬虫介绍

1.1 what is 爬虫?

- 形象概念: 爬虫,即网络爬虫,大家可以理解为在网络上爬行的一直蜘蛛,互联网就比作一张大网,而爬虫便是在这张网上爬来爬去的蜘蛛咯,如果它遇到资源,那么它就会抓取下来。想抓取什么?这个由你来控制它。

- 学术概念:爬虫就是通过编写程序模拟浏览器上网,让其去互联网上抓取数据的过程。

1.2 爬虫的价值

之前在授课过程中,好多同学都问过我这样的一个问题:为什么要学习爬虫,学习爬虫能够为我们以后的发展带来那些好处?其实学习爬虫的原因和为我们以后发展带来的好处都是显而易见的,无论是从实际的应用还是从就业上。

抓取互联网上的数据,为我所用,有了大量的数据,就如同有了一个数据银行一样,下一步做的就是如何将这些爬取的数据产品化,商业化。

从就业的角度来说,爬虫工程师目前来说属于紧缺人才,并且薪资待遇普遍较高所以,深层次地掌握这门技术,对于就业来说,是非常有利的。有些人学习爬虫可能为了就业或者跳槽。从这个角度来说,爬虫工程师是不错的选择之一。随着大数据时代的来临,爬虫技术的应用将越来越广泛,在未来会拥有更好的发展空间。

1.3 爬虫是合法还是违法的?

爬虫作为一种计算机技术就决定了它的中立性,因此爬虫本身在法律上并不被禁止,但是利用爬虫技术获取数据这一行为是具有违法甚至是犯罪的风险的。所谓具体问题具体分析,正如水果刀本身在法律上并不被禁止使用,但是用来捅人,就不被法律所容忍了。

或者我们可以这么理解:爬虫是用来批量获得网页上的公开信息的,也就是前端显示的数据信息。因此,既然本身就是公开信息,其实就像浏览器一样,浏览器解析并显示了页面内容,爬虫也是一样,只不过爬虫会批量下载而已,所以是合法的。不合法的情况就是配合爬虫,利用黑客技术攻击网站后台,窃取后台数据(比如用户数据等)。

举个例子:像谷歌这样的搜索引擎爬虫,每隔几天对全网的网页扫一遍,供大家查阅,各个被扫的网站大都很开心。这种就被定义为“善意爬虫”。但是像抢票软件这样的爬虫,对着 12306 每秒钟恨不得撸几万次,铁总并不觉得很开心,这种就被定义为“恶意爬虫”。

1.4 爬虫的分类

通用爬虫:通用爬虫是搜索引擎(Baidu、Google、Yahoo等)“抓取系统”的重要组成部分。主要目的是将互联网上的网页下载到本地,形成一个互联网内容的镜像备份。 简单来讲就是尽可能的;把互联网上的所有的网页下载下来,放到本地服务器里形成备分,在对这些网页做相关处理(提取关键字、去掉广告),最后提供一个用户检索接口。

聚焦爬虫:聚焦爬虫是根据指定的需求抓取网络上指定的数据。例如:获取豆瓣上电影的名称和影评,而不是获取整张页面中所有的数据值。

增量式爬虫:增量式是用来检测网站数据更新的情况,且可以将网站更新的数据进行爬取(后期会有章节单独对其展开详细的讲解)。

1.5 robots协议

几乎是和爬虫技术诞生的同一时刻,反爬虫技术也诞生了。在90年代开始有搜索引擎网站利用爬虫技术抓取网站时,一些搜索引擎从业者和网站站长通过邮件讨论定下了一项“君子协议”—— robots.txt。即网站有权规定网站中哪些内容可以被爬虫抓取,哪些内容不可以被爬虫抓取。这样既可以保护隐私和敏感信息,又可以被搜索引擎收录、增加流量。

历史上第一桩关于爬虫的官司诞生在2000年,eBay将一家聚合价格信息的比价网站BE告上了法庭,eBay声称自己已经将哪些信息不能抓取写进了robots协议中,但BE违反了这一协议。但BE认为eBay上的内容属于用户集体贡献而不归用户所有,爬虫协议不能用作法律参考。最后经过业内反复讨论和法庭上的几轮唇枪舌战,最终以eBay胜诉告终,也开了用爬虫robots协议作为主要参考的先河。

最后,可以通过网站域名 + /robots.txt的形式访问该网站的协议详情,例如:www.taobao.com/robots.txt

2.快速使用 requests

# 安装
pip3 install requests
import requests

# res = requests.get('https://www.cnblogs.com/liuqingzheng/p/16005866.html')
# print(res.text)


# 如果有的网站,发送请求,不返回数据,人家做了反扒---》拿不到数据,学习如何反扒
# res = requests.get('https://dig.chouti.com/')
# print(res.text)

二:requests

1.Requests模块简介

官方链接:http://docs.python-requests.org/en/master/

# 介绍:使用 requests 可以模拟浏览器的请求,比起之前用到的 urllib,requests 模块的 api 更加便捷(本质就是封装了 urllib3)

# 注意:requests 库发送请求将网页内容下载下来以后,并不会执行 js 代码,这需要我们自己分析目标站点然后发起新的 request 请求

# 安装:pip3 install requests

# 各种请求方式:常用的就是 requests.get() 和 requests.post()
>>> import requests
>>> r = requests.get('https://api.github.com/events')
>>> r = requests.post('http://httpbin.org/post', data = {'key':'value'})
>>> r = requests.put('http://httpbin.org/put', data = {'key':'value'})
>>> r = requests.delete('http://httpbin.org/delete')
>>> r = requests.head('http://httpbin.org/get')
>>> r = requests.options('http://httpbin.org/get')
# 建议在正式学习 requests 前,先熟悉下HTTP协议https://www.cnblogs.com/liuqingzheng/p/10191056.html

2.基于GET请求

2.1 基本请求

import requests
response=requests.get('http://dig.chouti.com/')
print(response.text)

2.2 带参数的GET请求

import requests
response=requests.get('https://www.baidu.com/s?wd=python&pn=1',
                      headers={
                        'User-Agent':'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.75 Safari/537.36',
                      })
print(response.text)
# response.encoding=response.apparent_encoding
with open('ptyhon.html','wb') as f:
    f.write(response.content)
#如果查询关键词是中文或者有其他特殊符号,则不得不进行url编码
from urllib.parse import urlencode
wd='美女'
encode_res=urlencode({'k':wd},encoding='utf-8')
keyword=encode_res.split('=')[1]
print(keyword)
# 然后拼接成url
url='https://www.baidu.com/s?wd=%s&pn=1' %keyword

response=requests.get(url,
                      headers={
                        'User-Agent':'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.75 Safari/537.36',
                      })

with open('girl.html','wb') as f:
    f.write(response.content)

2.3 带参数的GET请求->headers

# 通常我们在发送请求时都需要带上请求头,请求头是将自身伪装成浏览器的关键,常见的有用的请求头如下
Host
Referer # 大型网站通常都会根据该参数判断请求的来源
User-Agent # 客户端
Cookie # Cookie信息虽然包含在请求头里,但requests模块有单独的参数来处理他,headers={}内就不要放它了
content-type 	# 数据类型
x-forward-for	# 客户端IP,也就是HTTP的请求端真实的IP,只有在通过了HTTP代理或者负载均衡服务器时才会添加该项,它不是RFC中定义的标准请求头信息
#添加headers(浏览器会识别请求头,不加可能会被拒绝访问,比如访问https://www.zhihu.com/explore)
import requests
response=requests.get('https://www.zhihu.com/explore')
response.status_code #500


#自己定制headers
headers={
    'User-Agent':'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.76 Mobile Safari/537.36',

}
respone=requests.get('https://www.zhihu.com/explore',
                     headers=headers)
print(respone.status_code) #200

2.4 带参数的GET请求->cookies

import requests

Cookies={   'user_session':'rzNme4L6LTH7QSresq8w0BVYhTNt5GS-asNnkOe7_FZ2CjB6',
}

response=requests.get('https://github.com/settings/emails',
             cookies=Cookies) #github对请求头没有什么限制,我们无需定制user-agent,对于其他网站可能还需要定制


print('306334678@qq.com' in response.text) #True

3.基于POST请求

3.1 介绍

#GET请求
HTTP默认的请求方法就是GET
     * 没有请求体
     * 数据必须在1K之内!
     * GET请求数据会暴露在浏览器的地址栏中

GET请求常用的操作:
       1. 在浏览器的地址栏中直接给出URL,那么就一定是GET请求
       2. 点击页面上的超链接也一定是GET请求
       3. 提交表单时,表单默认使用GET请求,但可以设置为POST


#POST请求
(1). 数据不会出现在地址栏中
(2). 数据的大小没有上限
(3). 有请求体
(4). 请求体中如果存在中文,会使用URL编码!


#!!!requests.post()用法与requests.get()完全一致,特殊的是requests.post()有一个data参数,用来存放请求体数据

3.2 发送post请求

模拟浏览器的登录行为。对于登录来说,应该输错用户名或密码然后分析抓包流程,用脑子想一想,输对了浏览器就跳转了,还分析个毛线,累死你也找不到包

3.2.1 流程分析

一 目标站点分析
    浏览器输入https://github.com/login
    然后输入错误的账号密码,抓包
    发现登录行为是post提交到:https://github.com/session
    而且请求头包含cookie
    而且请求体包含:
        commit:Sign in
        utf8:✓
        authenticity_token:lbI8IJCwGslZS8qJPnof5e7ZkCoSoMn6jmDTsL1r/m06NLyIbw7vCrpwrFAPzHMep3Tmf/TSJVoXWrvDZaVwxQ==
        login:egonlin
        password:123


二 流程分析
    先GET:https://github.com/login拿到初始cookie与authenticity_token
    返回POST:https://github.com/session, 带上初始cookie,带上请求体(authenticity_token,用户名,密码等)
    最后拿到登录cookie

    ps:如果密码时密文形式,则可以先输错账号,输对密码,然后到浏览器中拿到加密后的密码,github的密码是明文

3.2.2 代码示例

import requests
import re

#第一次请求
r1=requests.get('https://github.com/login')
r1_cookie=r1.cookies.get_dict() #拿到初始cookie(未被授权)
authenticity_token=re.findall(r'name="authenticity_token".*?value="(.*?)"',r1.text)[0] #从页面中拿到CSRF TOKEN

#第二次请求:带着初始cookie和TOKEN发送POST请求给登录页面,带上账号密码
data={
    'commit':'Sign in',
    'utf8':'✓',
    'authenticity_token':authenticity_token,
    'login':'317828332@qq.com',
    'password':'alex3714'
}
r2=requests.post('https://github.com/session',
             data=data,
             cookies=r1_cookie
             )


login_cookie=r2.cookies.get_dict()


#第三次请求:以后的登录,拿着login_cookie就可以,比如访问一些个人配置
r3=requests.get('https://github.com/settings/emails',
                cookies=login_cookie)

print('317828332@qq.com' in r3.text) #True

4.响应Response

4.1 requests 属性

import requests
respone=requests.get('http://www.jianshu.com')
# respone属性
print(respone.text)
print(respone.content)

print(respone.status_code)
print(respone.headers)
print(respone.cookies)
print(respone.cookies.get_dict())
print(respone.cookies.items())

print(respone.url)
print(respone.history)

print(respone.encoding)

#关闭:response.close()
from contextlib import closing
with closing(requests.get('xxx',stream=True)) as response:
    for line in response.iter_content():
        pass

4.2 设置编码

#编码问题
import requests
response=requests.get('http://www.autohome.com/news')
# response.encoding='gbk' #汽车之家网站返回的页面内容为gb2312编码的,而requests的默认编码为ISO-8859-1,如果不设置成gbk则中文乱码
print(response.text)

4.3 获取二进制数据

import requests

response=requests.get('https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1509868306530&di=712e4ef3ab258b36e9f4b48e85a81c9d&imgtype=0&src=http%3A%2F%2Fc.hiphotos.baidu.com%2Fimage%2Fpic%2Fitem%2F11385343fbf2b211e1fb58a1c08065380dd78e0c.jpg')

with open('a.jpg','wb') as f:
    f.write(response.content)

5.高级用法

5.1 SSL Cert Verification

#证书验证(大部分网站都是https)
import requests
respone=requests.get('https://www.12306.cn') #如果是ssl请求,首先检查证书是否合法,不合法则报错,程序终端


#改进1:去掉报错,但是会报警告
import requests
respone=requests.get('https://www.12306.cn',verify=False) #不验证证书,报警告,返回200
print(respone.status_code)


#改进2:去掉报错,并且去掉警报信息
import requests
from requests.packages import urllib3
urllib3.disable_warnings() #关闭警告
respone=requests.get('https://www.12306.cn',verify=False)
print(respone.status_code)

#改进3:加上证书
#很多网站都是https,但是不用证书也可以访问,大多数情况都是可以携带也可以不携带证书
#知乎\百度等都是可带可不带
#有硬性要求的,则必须带,比如对于定向的用户,拿到证书后才有权限访问某个特定网站
import requests
respone=requests.get('https://www.12306.cn',
                     cert=('/path/server.crt',
                           '/path/key'))
print(respone.status_code)

5.2 使用代理

#官网链接: http://docs.python-requests.org/en/master/user/advanced/#proxies

#代理设置:先发送请求给代理,然后由代理帮忙发送(封ip是常见的事情)
import requests
proxies={
    'http':'http://egon:123@localhost:9743',#带用户名密码的代理,@符号前是用户名与密码
    'http':'http://localhost:9743',
    'https':'https://localhost:9743',
}
respone=requests.get('https://www.12306.cn',
                     proxies=proxies)

print(respone.status_code)



#支持socks代理,安装:pip install requests[socks]
import requests
proxies = {
    'http': 'socks5://user:pass@host:port',
    'https': 'socks5://user:pass@host:port'
}
respone=requests.get('https://www.12306.cn',
                     proxies=proxies)

print(respone.status_code)

5.3 超时设置

#超时设置
#两种超时:float or tuple
#timeout=0.1 #代表接收数据的超时时间
#timeout=(0.1,0.2)#0.1代表链接超时  0.2代表接收数据的超时时间

import requests
respone=requests.get('https://www.baidu.com',
                     timeout=0.0001)

5.4 认证设置

#官网链接:http://docs.python-requests.org/en/master/user/authentication/

#认证设置:登陆网站是,弹出一个框,要求你输入用户名密码(与alter很类似),此时是无法获取html的
# 但本质原理是拼接成请求头发送
#         r.headers['Authorization'] = _basic_auth_str(self.username, self.password)
# 一般的网站都不用默认的加密方式,都是自己写
# 那么我们就需要按照网站的加密方式,自己写一个类似于_basic_auth_str的方法
# 得到加密字符串后添加到请求头
#         r.headers['Authorization'] =func('.....')

#看一看默认的加密方式吧,通常网站都不会用默认的加密设置
import requests
from requests.auth import HTTPBasicAuth
r=requests.get('xxx',auth=HTTPBasicAuth('user','password'))
print(r.status_code)

#HTTPBasicAuth可以简写为如下格式
import requests
r=requests.get('xxx',auth=('user','password'))
print(r.status_code)

5.5 异常处理

#异常处理
import requests
from requests.exceptions import * #可以查看requests.exceptions获取异常类型

try:
    r=requests.get('http://www.baidu.com',timeout=0.00001)
except ReadTimeout:
    print('===:')
# except ConnectionError: #网络不通
#     print('-----')
# except Timeout:
#     print('aaaaa')

except RequestException:
    print('Error')

5.6 上传文件

import requests
files={'file':open('a.jpg','rb')}
respone=requests.post('http://httpbin.org/post',files=files)
print(respone.status_code)

三:代理池

1.搭建代理池

# requests 发送请求使用代理
# 代理从哪来
	-公司花钱买
    -搭建免费的代理池:https://github.com/jhao104/proxy_pool
        -python:爬虫+flask写的
        -架构:看下图
        
        
# 搭建步骤:
	1 git clone https://github.com/jhao104/proxy_pool.git
    2 使用pycharm打开
    3 安装依赖:pip install -r requirements.txt
    4 修改配置文件(redis地址即可)
        HOST = "0.0.0.0"
        PORT = 5010
        DB_CONN = 'redis://127.0.0.1:6379/0'
        PROXY_FETCHER #爬取哪些免费代理网站
   	5 启动爬虫程序
    python proxyPool.py schedule
	6 启动服务端
    python proxyPool.py server
    
    7 使用随机一个免费代理
    地址栏中输入:http://127.0.0.1:5010/get/
        
        
        
# 使用随机代理发送请求
import requests
from requests.packages import urllib3
urllib3.disable_warnings() #关闭警告
# 获取代理
res = requests.get('http://127.0.0.1:5010/get/').json()
proxies = {}
if res['https']:
    proxies['https'] = res['proxy']
else:
    proxies['http'] = res['proxy']
print(proxies)
res = requests.post('https://www.cnblogs.com', proxies=proxies,verify=False)
# res = requests.post('https://www.cnblogs.com')
print(res)

2.django获取客户端的ip

# 建立django后端---》index地址---》访问就返回访问者的ip


# django代码---》不要忘记改配置文件
# 路由
path('', index),
# 视图函数
def index(request):
    ip = request.META.get('REMOTE_ADDR')
    print('ip地址是', ip)
    return HttpResponse(ip)



# 测试端:

# import requests
# from requests.packages import urllib3
# urllib3.disable_warnings() #关闭警告
# # 获取代理
# res = requests.get('http://127.0.0.1:5010/get/').json()
# proxies = {}
# if res['https']:
#     proxies['https'] = res['proxy']
# else:
#     proxies['http'] = res['proxy']
#
# print(proxies)
# res=requests.get('http://101.43.19.239/', proxies=proxies,verify=False)
# print(res.text)


from threading import Thread
import requests


def task():
    res = requests.get('http://101.43.19.239/')
    print(res.text)


for i in range(1000):
    t = Thread(target=task)
    t.start()

四:lxml

1.简介

lxml 是 python 的一个解析库,支持HTML和XML解析,同时支持XPath解析方式。Lxml的解析速率相较BeautifulSoup更高,后者学习相较更简单。

xml 是一个树形结构,lxml 使用 etree._Element 和 etree._ElementTree 来分别代表树中的节点和树,etree.ELement 和 etree.ElementTree 分别是两个工厂函数。

功能:用于解析HTML与XML文件;进行文件读取;etree 和 Xpath 的配合使用

2.安装

# 安装
pip install lxml

# 官方文档
https://lxml.de/index.html

3.快速使用

import requests
from lxml import etree

head_show = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36",
}

req_txt = requests.get(url='https://tianqi.so.com/weather/101020600', headers=head_show, timeout=30)
if req_txt.status_code == 200:
    tree = etree.HTML(req_txt.text)
    tree_lit = tree.xpath('//*[@id="china-weather"]/div[1]/div/div[4]/div[1]/div/ul')
    for lit in tree_lit:
        date = lit.xpath('./li/div[2]/text()')[0]
        tianqi = lit.xpath('./li/div[4]/text()')[0].replace('\n', '').strip(' ')
        wendu = lit.xpath('./li/div[5]/text()')[0]
        fengxiang = lit.xpath('./li/div[6]/text()')[0]
        print(f'日期:{date}  天气:{tianqi}  温度:{wendu}  风向:{fengxiang}')

4.Xpath语法

4.1 选取节点

Xpath使用路径表达式在XML文档中选取节点。节点是经过沿着路径或者step来选取的。经常使用的路径表达式以下:

表达式 描述
nodename 选取此节点的全部子节点
/ 从根节点选取
// 从匹配选择的当前节点选择文档的节点,而不考虑它们的位置
. 选取当前节点
.. 选取当前节点的父节点
@ 选取属性

下面为一些路径表达式及表达式结果:

路径表达式 结果
bookstore 选取 bookstore 元素的全部子节点
/bookstore 选取根元素 bookstore。注释:假如路径起始于正斜杠( / ),则此路径始终表明到某元素的绝对路径!
bookstore/book 选取属于 bookstore 的子元素的全部 book 元素
//book 选取全部 book 子元素,而无论它们在文档中的位置。
bookstore//book 选择属于 bookstore 元素的后代的全部 book 元素,而无论它们位于 bookstore 之下的什么位置。
//@lang 选取名为 lang 的全部属性。

4.2 谓语(Predicates)

谓语用来查找某个特定的节点或者包含某个指定的值的节点。谓语被嵌在方括号中。

下面为一些带有谓语的路径表达式,及表达式结果:

路径表达式 结果
/bookstore/book[1] 选取属于 bookstore 子元素的第一个 book 元素。
/bookstore/book[last()] 选取属于 bookstore 子元素的最后一个 book 元素。
/bookstore/book[last()-1] 选取属于 bookstore 子元素的倒数第二个 book 元素。
/bookstore/book[position()❤️] 选取最前面的两个属于 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。

4.3 选取未知节点

XPath通配符可用来选取未知的XML元素。

配符 描述
* 匹配任何元素节点
@* 匹配任何属性节点
node() 匹配任何类型的节点

下面为一些带有通配符的路径表达式,及表达式结果:

路径表达式 结果
/bookstore/* 选取 bookstore 元素的全部子元素。
//* 选取文档中的全部元素。
//title[@*] 选取文档中的全部元素。

4.4 选取若干路径

经过在路径表达式中使用“|”运算符,您能够选取若干个路径。

下面为一些带有"|"运算符的路径表达式,及表达式结果:

路径表达式 结果
//book/title //book/price
//title //price
/bookstore/book/title //price

4.4 Xpath运算符

下面列出了可用再XPath表达式中运算符:

运算符 描述 实例 返回值
计算两个节点集 //book
+ 加法 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

5.爬虫中运用XPath

5.1 操作步骤

# 导包
from lxml import etree

# 建立 etree 对象进行指定数据的解析
# 本地
etree=etree.parse('本地文件路径')
etree.xpath('xpath表达式')

# 网络
etree=etree.HTML('网络请求到的页面数据')
etree.xpath('xpath表达式')

5.2 常用的表达式

# 常用

属性定位:
    #找到class属性值为song的div标签
    //div[@class="song"] 

层级&索引定位:
    #找到class属性值为tang的div的直系子标签ul下的第二个子标签li下的直系子标签a
    //div[@class="tang"]/ul/li[2]/a
    
取文本:
    # /表示获取某个标签下的文本内容
    # //表示获取某个标签下的文本内容和全部子标签下的文本内容
    //div[@class="song"]/p[1]/text()
    //div[@class="tang"]//text()
    
取属性:
    //div[@class="tang"]//li[2]/a/@href
# 了解

逻辑运算:
    #找到href属性值为空且class属性值为du的a标签
    //a[@href="" and @class="du"]
模糊匹配:
    //div[contains(@class, "ng")]
    //div[starts-with(@class, "ta")]

5.3 示例

建立本地html文件来测试Xpath数据解析。

test.html

<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>
# test.py

from lxml import etree

# 建立etree对象进行制定数据解析
tree = etree.parse('./test.html')    # 解析本地文件

# 属性定位:根据指定的属性定位到指定的节点标签
# tree.xpath('//div[@class="song"]')
"""
[<Element div at 0x10dbead88>]
"""

# 层级&索引定位
# tree.xpath('//div[@class="tang"]/ul/li[2]/a')  # 定位到第二个li中的a标签
"""
[<Element a at 0x10e30ab48>]
"""

# 逻辑定位  //a:整个源码下的a标签   
# [@href="" and @class="du"] href为空,class属性为du
# tree.xpath('//a[@href="" and @class="du"]')
"""
[<Element a at 0x10e2a8e88>]
"""

# 模糊匹配
# tree.xpath('//div[contains(@class, "ng")]')
# tree.xpath('//div[starts-with(@class, "ta")]')
"""
[<Element div at 0x10dbea548>, <Element div at 0x10e392348>]
[<Element div at 0x10dabd688>]
"""

# 取文本
# /text()获取当前标签中直系存储的文本数据
# tree.xpath('//div[@class="song"]/p[1]/text()')    # song的div下的第一个p子标签
# 输出:['李清照']

# //text()获取tang这个div标签下全部子标签中存储的文本数据
# tree.xpath('//div[@class="tang"]//text()')
"""
['\n\t\t',
 '\n\t\t\t',
 '清明时节雨纷纷,路上行人欲断魂,借问酒家何处有,牧童遥指杏花村',
 '\n\t\t\t',
 '秦时明月汉时关,万里长征人未还,但使龙城飞将在,不教胡马度阴山',
 '\n\t\t\t',
 '岐王宅里寻常见,崔九堂前几度闻,正是江南好风景,落花时节又逢君',
 '\n\t\t\t',
 '杜甫',
 '\n\t\t\t',
 '杜牧',
 '\n\t\t\t',
 '杜小月',
 '\n\t\t\t',
 '度蜜月',
 '\n\t\t\t',
 '凤凰台上凤凰游,凤去台空江自流,吴宫花草埋幽径,晋代衣冠成古丘',
 '\n\t\t',
 '\n\t']
"""

# 取属性
tree.xpath('//div[@class="tang"]//li[2]/a/@href')  # 指定a标签对应的href的值
"""
['http://www.163.com']
"""

五:selenium

1.selenium

见我的另一篇博客:https://www.cnblogs.com/ysging/p/12695157.html

2.selenium其它用法

5.1 查找标签

# 两个方法
bro.find_element   找一个
bro.find_elements  找所有

# 可以按id,标签名,name属性名,类名,a标签的文字,a标签的文字模糊匹配,css选择器,xpath【后面聊】
# input_1=bro.find_element(by=By.ID,value='wd')  # 按id找
# input_1 = bro.find_element(by=By.NAME, value='wd')  # name属性名
# input_1=bro.find_element(by=By.TAG_NAME,value='input') # 可以按标签名字找
# input_1=bro.find_element(by=By.CLASS_NAME,value='s_ipt') # 可以按类名
# input_1=bro.find_element(by=By.LINK_TEXT,value='登录') # 可以按a标签内容找
# input_1=bro.find_element(by=By.PARTIAL_LINK_TEXT,value='录') # 可以按a标签内容找
# input_1 = bro.find_element(by=By.CSS_SELECTOR, value='#su')  # 可以按css选择器

5.2 获取位置属性大小,文本

print(tag.get_attribute('src'))  # 用的最多
tag.text  # 文本内容
#获取标签ID,位置,名称,大小(了解)
print(tag.id)
print(tag.location)
print(tag.tag_name)
print(tag.size)

5.3 元素操作

# 点击
tag.click()

# 输入内容
tag.send_keys()

# 清空内容
tag.clear()


# 浏览器对象 最大化
bro.maximize_window() 
#浏览器对象  截全屏
bro.save_screenshot('main.png') 

5.4 切换选项卡

import time
from selenium import webdriver

browser=webdriver.Chrome(executable_path='chromedriver.exe')
browser.get('https://www.baidu.com')
browser.execute_script('window.open()')

print(browser.window_handles) #获取所有的选项卡


browser.switch_to.window(browser.window_handles[1])
browser.get('https://www.taobao.com')
time.sleep(2)
browser.switch_to.window(browser.window_handles[0])
browser.get('https://www.sina.com.cn')
browser.close()

六:示例

1.爬取某视频网站

import requests
import re

res = requests.get('https://www.pearvideo.com/category_loading.jsp?reqType=5&categoryId=1&start=0')
# print(res.text)
# 解析出真正视频地址
video_list = re.findall('<a href="(.*?)" class="vervideo-lilink actplay">', res.text)
# print(video_list)
for i in video_list:
    # i='video_1212452'
    video_id = i.split('_')[-1]
    real_url = 'https://www.pearvideo.com/' + i
    # print('真正视频地址是:',real_url)
    headers = {
        'Referer': 'https://www.pearvideo.com/video_%s' % video_id
    }
    res1 = requests.get('https://www.pearvideo.com/videoStatus.jsp?contId=%s&mrd=0.29636538326105044' % video_id,
                        headers=headers).json()
    # print(res1["videoInfo"]['videos']['srcUrl'])
    mp4_url = res1["videoInfo"]['videos']['srcUrl']
    mp4_url = mp4_url.replace(mp4_url.split('/')[-1].split('-')[0], 'cont-%s' % video_id)
    print(mp4_url)
    res2 = requests.get(mp4_url)
    with open('./video/%s.mp4' % video_id, 'wb') as f:
        for line in res2.iter_content():
            f.write(line)

# headers={
#     'Referer': 'https://www.pearvideo.com/video_1212452'
# }
# res=requests.get('https://www.pearvideo.com/videoStatus.jsp?contId=1212452&mrd=0.29636538326105044',headers=headers)
#
# print(res.text)


# https://video.pearvideo.com/mp4/short/20171204/    1678938313577    -11212458-hd.mp4
# https://video.pearvideo.com/mp4/short/20171204/     cont-1212452    -11212458-hd.mp4

mp4_url = 'https://video.pearvideo.com/mp4/short/20171204/  1678938313577-11212458-hd.mp4'

附件

参考博客:https://www.cnblogs.com/liuqingzheng/category/1216260.html

Scrapy框架

Appium:https://www.cnblogs.com/deepstone/p/4166495.html

官网:https://gitee.com/foxcrane/appium/
     http://appium.io/
posted @ 2023-03-17 10:58  亦双弓  阅读(35)  评论(0)    收藏  举报