1. 爬虫简介
爬虫一般指网络爬虫。 网络爬虫(又称为网页蜘蛛,网络机器人,在FOAF社区中间,更经常的称为网页追逐者),是一种按照一定的规则,自动地抓取万维网信息的程序或者脚本。另外一些不常使用的名字还有蚂蚁、自动索引、模拟程序或者蠕虫
大部分的软件 cs 或 bs,主流都是用 http 协议通信,实际上爬虫就是模拟发送 http 请求,例如 Postman 也可以模拟发送,爬虫则是在 python 中使用代码进行模拟发送请求。服务端把数据返回( html,xml,json ),在进行数据的清洗(re,bs4),清洗完后再入库(文件,mysql,redis,es,mongo)
mysql: tcp自定定制的协议
redis: tcp自定定制的协议
docker:http协议,符合resful规范
es: http协议,符合resful规范
python 中使用 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')
2. requests 模块介绍
在 python 中模拟发送请求使用 requests 模块,或者使用 urllib 内置模块,但是其 api 使用复杂。
该模块不仅可以用作爬虫,在后端跟另一个服务交互,也需要使用它
例如公司有一个长链转成短链的服务(把很长的url链接生成短的url链接),可以申请一个域名,将长链和自
己设置的短链进行绑定在库中,并加到自己的域名,当访问短链时会重定向到长链所在地址。
2.1 requests get 请求
HTTP默认的请求方法就是GET
- 没有请求体
- 数据必须在1K之内
- GET请求数据会暴露在浏览器的地址栏中
GET请求常用的操作:
- 在浏览器的地址栏中直接给出 URL,那么就一定是 GET 请求
- 点击页面上的超链接也一定是 GET 请求
- 提交表单时,表单默认使用 GET 请求,但可以设置为 POST
基础使用
import requests
res = requests.get('https://www.1biqug.com/')
添加 params 参数
import requests
res = requests.get('https://www.cnblogs.com/', params={'name':'xwx','age':19})
注意点:如果地址中包含中文则涉及到 url 的编码和解码,需要使用 urllib.parse.quote 和 urllib.parse.unquote 处理
例如路由中含 ‘谢帅哥’ 中文,复制下来为: https://blog.csdn.net/m0_58987515?type=blog&name=%E8%B0%A2%E5%B8%85%E5%93%A5
from urllib import parse
url = '哈哈哈'
res = parse.quote(url)
print(res)
res = parse.unquote(url)
print(res)
添加请求头
常见的请求头参数有
参数 | 说明 |
---|
Host | 指明了服务器的域名及服务器监听的TCP端口号。 | Referer | 告诉服务器该网页是从哪个页面链接过来。 | Accept-Charset | 规定服务器处理表单数据所接受的字符集。(常用字符集有 UTF-8-Unicode等) | Accept-Language | 告知服务器用户代理能够处理的自然语言集。 | Authorization | 告知服务器客户端的Web认证信息。 | User-Agent | 告知服务器HTTP 客户端程序的信息。 |
解决简单的反扒需要获取 user-agent 添加到请求头中,如下示例
header = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36',
}
res = requests.get('https://dig.chouti.com/', headers=header)
print(res.text)
添加 cookie
添加了 cookie 后会有登录信息,才能操作登录后相关操作。
import requests
header = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36',
}
res = requests.post('https://dig.chouti.com/link/vote',
headers=header,
data={
'linkId': '35811284'
})
Cookie信息虽然包含在请求头里,但requests模块有单独的参数来处理他,headers={}内就不要放它了,cookie 是 CookieJar 的对象
import requests
Cookies={
'user_session':'wGMHFJKgDcmRIVvcA14_Wrt_3xaUyJNsBnPbYzEL6L0bHcfc',
}
response=requests.get('https://github.com/settings/emails', cookies=Cookies)
print('378533872@qq.com' in response.text)
2.2 post 请求
POST请求
- 数据不会出现在地址栏中
- 数据的大小没有上限
- 有请求体
- 请求体中如果存在中文,需要使用URL编码
requests.post() 用法与 requests.get() 完全一致,特殊的是 requests.post() 有一个data参数,用来存放请求体数据
常见响应标头
标头 | 说明 |
---|
Keep-Alive | 表示 Connection 非持续链接的存活时间。 | Server | 包含有关原始服务器用来处理请求的软件的信息。 | Set-Cookie | 用于服务器向客户端发送 sessionID。 | Transfer-Encoding | 规定了传输报文主题时采用的编码方式。 | Location | 令客户端重定向至指定的URI。 | WWW-Authenticate | 表示服务器对客户端的认证信息。 |
携带数据
请求的数据格式有:from-data、urlencoded(默认)、json
import requests
res = requests.post('http://www.aa7a.cn/user.php', data={
'username': '111111',
'password': '111111',
'captcha': '111111',
'remember': 1,
'ref': 'http://www.aa7a.cn',
'act': ' act_login',
})
print(res.cookies)
res1 = requests.get('http://www.aa7a.cn/', cookies=res.cookies)
print('123@qq.com' in res1.text)
携带 json 数据
携带 json 数据可以在 json 参数中,如下所示
res=requests.post('xxx', json={})
request.session
request.session 的作用是在整个过程中自动维护 cookie
session=requests.session()
session.post('http://www.aa7a.cn/user.php', data={
'username': '123@qq.com',
'password': '123',
'captcha': 'aaaa',
'remember': 1,
'ref': 'http://www.aa7a.cn/user.php?act=logout',
'act': ' act_login',
})
res1=session.get('http://www.aa7a.cn/')
print('123@qq.com' in res1.text)
2.3 response 属性
repsonse对象的属性和方法,是把 http 的响应封装成了 response
属性方法 | 说明 |
---|
respone.text | 响应体的字符串 | respone.content | 响应体二进制数据 | respone.status_code | 响应状态码 | respone.headers | 响应头 | respone.cookies | 响应的 cookie | respone.cookies.get_dict() | cookie 转成 dict | respone.cookies.items() | cookie 拿出 key 和 value | respone.url | 请求的地址 | respone.history | 列表,有重定向,里面放了重定向之前的地址 | respone.encoding | 响应编码格式 | respone.iter_content() | 下载图片,视频,需要使用它,可以使用 chunk_size 指定字节大小 |
with open('致命诱惑3.mp4','wb') as f:
f.write(res.content)
for line in res.iter_content(chunk_size=1024):
f.write(line)
2.4 编码问题
若出现中文乱码,可以指定编码的格式。大部分网站都是 utf-8 编码,老网站中文编码使用 gbk,gb2312。
respone = requests.get('http://www.autohome.com/news')
respone.encoding='gbk'
print(respone.text)
2.5 获取二进制数据
response.content
response.iter_content(chunk_size=1024)
res=requests.get('https://gd-hbimg.huaban.com/e1abf47cecfe5848afc2a4a8fd2e0df1c272637f2825b-e3lVMF_fw658')
with open('a.png','wb') as f:
f.write(res.content)
2.6 解析 json
import requests
response=requests.get('http://httpbin.org/get')
import json
res1=json.loads(response.text)
res2=response.json()
print(res1 == res2)
2.7 高级用法之 Cert Verification
高级用法之证书
import requests
respone=requests.get('https://www.12306.cn')
import requests
respone=requests.get('https://www.12306.cn',verify=False)
print(respone.status_code)
import requests
from requests.packages import urllib3
urllib3.disable_warnings()
respone=requests.get('https://www.12306.cn',verify=False)
print(respone.status_code)
import requests
respone=requests.get('https://www.12306.cn',
cert=('/path/server.crt',
'/path/key'))
print(respone.status_code)
2.8 代理
代理简单来说就是使用别人的 IP 来访问资源,并返回到自己这。
国内免费 HTTP 代理
import requests
proxies = {
'http': '112.14.47.6:52024',
}
respone = requests.get('https://www.cnblogs.com/', proxies=proxies)
print(respone.status_code)
2.9 超时,认证,异常,上传文件
超时设置
import requests
respone = requests.get('https://www.baidu.com', timeout=0.0001)
异常处理
from requests.exceptions import *
try:
r = requests.get('http://www.baidu.com', timeout=0.00001)
except ReadTimeout:
print('===:')
except ConnectionError:
print('-----')
except Timeout:
print('aaaaa')
except Exception:
print('x')
上传文件
import requests
files = {'file': open('a.jpg', 'rb')}
respone = requests.post('http://httpbin.org/post', files=files)
print(respone.status_code)
3. 代理池
3.1 搭建简易代理池
可以使用 proxy_pool 来搭建简单的代理池,官网:proxy_pool
简易高效的代理池,提供如下功能:
- 定时抓取免费代理网站,简易可扩展。
- 使用 Redis 对代理进行存储并对代理可用性进行排序。
- 定时测试和筛选,剔除不可用代理,留下可用代理。
- 提供代理 API,随机取用测试通过的可用代理。
第一步:clone代码
git clone git@github.com:jhao104/proxy_pool.git
第二步:安装依赖
pip3 install -r requirements.txt
第三步:修改配置文件 settings.py
DB_CONN = 'redis://127.0.0.1:6379/1'
第四步:启动项目
python3 proxyPool.py schedule
python3 proxyPool.py server
第五步:获取代理
http://127.0.0.1:5010/get/
3.2 django 后端获取客户端的 ip
import requests
'''
{
"anonymous":"",
"check_count":1,
"fail_count":0,
"https":false,
"last_status":true,
last_time":"2022-08-01 17:47:29",
"proxy":"183.250.163.175:9091",
"region":"",
"source":"freeProxy08/freeProxy06"
}
'''
res = requests.get('http://127.0.0.1:5010/get/').json()
print(res['proxy'])
h = 'https' if res['https'] else h = 'http'
proxies = {
h: res['proxy'],
}
res1 = requests.get('http://121.4.75.248/gip/', proxies=proxies)
print(res1.text)
注意点:
- 服务器的sqlit3版本可能会出问题,可以提前配置好MySQL数据库去迁移文件。
- 部署端口的时候如果失败可以关闭nginx ** nginx -s stop**
- 部署的语句:python manage.py runserver 0.0.0.0:80
4. 小案例
4.1 爬取视频
以梨视频为例
import requests
import re
res = requests.get('https://www.pearvideo.com/category_loading.jsp?reqType=5&categoryId=5&start=0')
video_list = re.findall('<a href="(.*?)" class="vervideo-lilink actplay">', res.text)
print(video_list)
for video in video_list:
video_id = video.split('_')[-1]
video_url = 'https://www.pearvideo.com/' + video
header = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36',
'Referer': video_url
}
res1 = requests.get('https://www.pearvideo.com/videoStatus.jsp?contId=%s&mrd=0.5602821872545047' % video_id,
headers=header
).json()
mp4_url = res1['videoInfo']['videos']['srcUrl']
real_mp4_url = mp4_url.replace(mp4_url.split('/')[-1].split('-')[0], 'cont-%s' % video_id)
print(real_mp4_url)
res2 = requests.get(real_mp4_url)
with open('video/%s.mp4' % video_id, 'wb') as f:
for line in res2.iter_content(1024):
f.write(line)
其他:
- 关于全站爬取:更换分类id和起始爬取的数字即可
- 同步爬取,速度一般,加入线程(线程池),提高爬取速度
- 封 ip 问题(使用代理池)
- 视频处理(截取视频,拼接视频使用 ffmpeg 软件,通过命令调用软件
- python操作软件:subprocess 模块 执行 ffmpeg 的命令完成视频操作
- python模块操作 opencv(c写的,编译后,使用python调用),实现非常高级的功能(文件操作给视频加头去尾部)
4.2 爬取新闻
以汽车之家为例。使用 bs4 解析
import requests
from bs4 import BeautifulSoup
res = requests.get('https://www.autohome.com.cn/news/1/#liststart')
soup = BeautifulSoup(res.text, 'html.parser')
ul_list = soup.find_all(name='ul', class_='article')
for ul in ul_list:
li_list = ul.find_all(name='li')
for li in li_list:
h3 = li.find(name='h3')
if h3:
title = h3.text
desc = li.find(name='p').text
url = 'http:' + li.find(name='a').attrs['href']
img = 'http:' + li.find(name='img')['src']
print('''
新闻标题:%s
新闻摘要:%s
新闻地址:%s
新闻图片:%s
''' % (title, desc, url, img))
4.3 爬取哔站视频
'''
通过该程序下载的视频和音频是分成连个文件的,没有合成,
视频为:视频名_video.mp4
音频为:视频名_audio.mp4
修改url的值,换成自己想下载的页面节课
'''
import requests
import json
import re
headers = {
'Accept': '*/*',
'Accept-Language': 'en-US,en;q=0.5',
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36'
}
def my_match(text, pattern):
match = re.search(pattern, text)
print(match.group(1))
print()
return json.loads(match.group(1))
def download_video(old_video_url, video_url, audio_url, video_name):
headers.update({"Referer": old_video_url})
print("开始下载视频:%s" % video_name)
video_content = requests.get(video_url, headers=headers)
print('%s视频大小:' % video_name, video_content.headers['content-length'])
audio_content = requests.get(audio_url, headers=headers)
print('%s音频大小:' % video_name, audio_content.headers['content-length'])
received_video = 0
with open('%s_video.mp4' % video_name, 'ab') as output:
while int(video_content.headers['content-length']) > received_video:
headers['Range'] = 'bytes=' + str(received_video) + '-'
response = requests.get(video_url, headers=headers)
output.write(response.content)
received_video += len(response.content)
audio_content = requests.get(audio_url, headers=headers)
received_audio = 0
with open('%s_audio.mp4' % video_name, 'ab') as output:
while int(audio_content.headers['content-length']) > received_audio:
headers['Range'] = 'bytes=' + str(received_audio) + '-'
response = requests.get(audio_url, headers=headers)
output.write(response.content)
received_audio += len(response.content)
return video_name
if __name__ == '__main__':
url = 'https://www.bilibili.com/video/BV1QG41187tj?'
res = requests.get(url, headers=headers)
playinfo = my_match(res.text, '__playinfo__=(.*?)</script><script>')
initial_state = my_match(res.text, r'__INITIAL_STATE__=(.*?);\(function\(\)')
video_url = playinfo['data']['dash']['video'][0]['baseUrl']
audio_url = playinfo['data']['dash']['audio'][0]['baseUrl']
video_name = initial_state['videoData']['title']
print('视频名字为:video_name')
print('视频地址为:', video_url)
print('音频地址为:', audio_url)
download_video(url, video_url, audio_url, video_name)
5. BeautifulSoup4 介绍
Beautiful Soup 是一个可以从HTML或XML文件中提取数据的Python库.它能够通过你喜欢的转换器实现惯用的文档导航,查找,修改文档的方式.Beautiful Soup会帮你节省数小时甚至数天的工作时间.你可能在寻找 Beautiful Soup3 的文档,Beautiful Soup 3 目前已经停止开发,官网推荐在现在的项目中使用Beautiful Soup 4, 移植到BS4
官方中文文档
pip install beautifulsoup4
Beautiful Soup支持Python标准库中的HTML解析器,还支持一些第三方的解析器,其中一个是 lxml .根据操作系统不同,可以选择下列方法来安装lxml:
$ apt-get install Python-lxml
$ easy_install lxml
$ pip install lxml
另一个可供选择的解析器是纯Python实现的 html5lib , html5lib的解析方式与浏览器相同,可以选择下列方法来安装html5lib:
$ apt-get install Python-html5lib
$ easy_install html5lib
$ pip install html5lib
下表列出了主要的解析器,以及它们的优缺点,官网推荐使用lxml作为解析器,因为效率更高. 在Python2.7.3之前的版本和Python3中3.2.2之前的版本,必须安装lxml或html5lib, 因为那些Python版本的标准库中内置的HTML解析方法不够稳定
解析器 | 使用方法 | 优势 | 劣势 |
---|
Python标准库 | BeautifulSoup(markup, “html.parser”) | Python的内置标准库 执行速度适中 文档容错能力强 | Python 2.7.3 or 3.2.2)前 的版本中文档容错能力差 | lxml HTML 解析器 | BeautifulSoup(markup, “lxml”) | 速度快 文档容错能力强 | 需要安装C语言库 | lxml XML 解析器 | BeautifulSoup(markup, [“lxml”, “xml”]) BeautifulSoup(markup, “xml”) | 速度快 唯一支持XML的解析器 | 需要安装C语言库 | html5lib | BeautifulSoup(markup, “html5lib”) | 最好的容错性 以浏览器的方式解析文档 生成HTML5格式的文档 | 速度慢 不依赖外部扩展 |
5.1 基本使用
html_doc = """
<html><head><title>The Dormouse's story</title></head>
<body>
<p class="title"><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
soup=BeautifulSoup(html_doc,'lxml')
res=soup.prettify()
print(res)
5.2 遍历文档树
html_doc = """
<html><head><title>The Dormouse's story</title></head>
<body>
<p id="my p" class="title"><b id="bbb" class="boldest">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
soup=BeautifulSoup(html_doc,'lxml')
print(soup.p)
print(soup.a)
print(soup.p.name)
print(soup.p.attrs)
print(soup.p['class'])
print(soup.p.string)
print(soup.p.strings)
print(soup.p.text)
for line in soup.stripped_strings:
print(line)
'''
如果tag包含了多个子节点,tag就无法确定 .string 方法应该调用哪个子节点的内容, .string 的输出结果是 None,如果只有一个子节点那么就输出该子节点的文本,比如下面的这种结构,soup.p.string 返回为None,但soup.p.strings就可以找到所有文本
<p id='list-1'>
哈哈哈哈
<a class='sss'>
<span>
<h1>aaaa</h1>
</span>
</a>
<b>bbbbb</b>
</p>
'''
print(soup.head.title.string)
print(soup.body.a.string)
print(soup.p.contents)
print(soup.p.children)
for i,child in enumerate(soup.p.children):
print(i,child)
print(soup.p.descendants)
for i,child in enumerate(soup.p.descendants):
print(i,child)
print(soup.a.parent)
print(soup.a.parents)
print('=====>')
print(soup.a.next_sibling)
print(soup.a.previous_sibling)
print(list(soup.a.next_siblings))
print(soup.a.previous_siblings)
5.3 搜索文档树
BeautifulSoup定义了很多搜索方法,这里着重介绍2个: find() 和 find_all()
5.3.1 五种过滤器
字符串、正则表达式、列表、True、方法
html_doc = """
<html><head><title>The Dormouse's story</title></head>
<body>
<p id="my p" class="title"><b id="bbb" class="boldest">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
soup=BeautifulSoup(html_doc,'lxml')
print(soup.find_all('b'))
import re
print(soup.find_all(re.compile('^b')))
print(soup.find_all(['a','b']))
print(soup.find_all(True))
for tag in soup.find_all(True):
print(tag.name)
print(soup.find_all(id=True))
def has_class_but_no_id(tag):
return tag.has_attr('class') and not tag.has_attr('id')
print(soup.find_all(has_class_but_no_id))
5.3.2 find_all( name , attrs , recursive , text , **kwargs )
print(soup.find_all(name=re.compile('^t')))
print(soup.find_all(id=re.compile('my')))
print(soup.find_all(href=re.compile('lacie'),id=re.compile('\d')))
print(soup.find_all(id=True))
data_soup = BeautifulSoup('<div data-foo="value">foo!</div>','lxml')
print(data_soup.find_all(attrs={"data-foo": "value"}))
print(soup.find_all('a',class_='sister'))
print(soup.find_all('a',class_='sister ssss'))
print(soup.find_all(class_=re.compile('^sis')))
print(soup.find_all('p',attrs={'class':'story'}))
print(soup.find_all(text='Elsie'))
print(soup.find_all('a',text='Elsie'))
print(soup.find_all('a',limit=2))
print(soup.html.find_all('a'))
print(soup.html.find_all('a',recursive=False))
'''
像调用 find_all() 一样调用tag
find_all() 几乎是Beautiful Soup中最常用的搜索方法,所以我们定义了它的简写方法. BeautifulSoup 对象和 tag 对象可以被当作一个方法来使用,这个方法的执行结果与调用这个对象的 find_all() 方法相同,下面两行代码是等价的:
soup.find_all("a")
soup("a")
这两行代码也是等价的:
soup.title.find_all(text=True)
soup.title(text=True)
'''
from bs4 import BeautifulSoup
c = 'asdasdasdnkajsdbadkasd'
res = BeautifulSoup(c, 'lxml')
print(res)
print(BeautifulSoup.find_all(res))
>> <html><body><p>asdasdasdnkajsdbadkasd</p></body></html>
>> [<html><body><p>asdasdasdnkajsdbadkasd</p></body></html>, <body><p>asdasdasdnkajsdbadkasd</p></body>, <p>asdasdasdnkajsdbadkasd</p>]
5.3.3 find( name , attrs , recursive , text , **kwargs )
find_all() 方法将返回文档中符合条件的所有tag,尽管有时候我们只想得到一个结果.比如文档中只有一个<body>标签,那么使用 find_all() 方法来查找<body>标签就不太合适, 使用 find_all 方法并设置 limit=1 参数不如直接使用 find() 方法.下面两行代码是等价的:
soup.find_all('title', limit=1)
soup.find('title')
唯一的区别是 find_all() 方法的返回结果是值包含一个元素的列表,而 find() 方法直接返回结果.
find_all() 方法没有找到目标是返回空列表, find() 方法找不到目标时,返回 None .
print(soup.find("nosuchtag"))
soup.head.title 是 tag的名字 方法的简写.这个简写的原理就是多次调用当前tag的 find() 方法:
soup.head.title
soup.find("head").find("title")
5.4 CSS选择器
div 标签名
.类名 类名
div>p div下紧邻的p标签
div p div下所有的p标签
html_doc = """
<html><head><title>The Dormouse's story</title></head>
<body>
<p class="title">
<b>The Dormouse's story</b>
Once upon a time there were three little sisters; and their names were
<a href="http://example.com/elsie" class="sister" id="link1">
<span>Elsie</span>
</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>;
<div class='panel-1'>
<ul class='list' id='list-1'>
<li class='element'>Foo</li>
<li class='element'>Bar</li>
<li class='element'>Jay</li>
</ul>
<ul class='list list-small' id='list-2'>
<li class='element'><h1 class='yyyy'>Foo</h1></li>
<li class='element xxx'>Bar</li>
<li class='element'>Jay</li>
</ul>
</div>
and they lived at the bottom of a well.
</p>
<p class="story">...</p>
"""
from bs4 import BeautifulSoup
soup=BeautifulSoup(html_doc,'lxml')
print(soup.p.select('.sister'))
print(soup.select('.sister span'))
print(soup.select('#link1'))
print(soup.select('#link1 span'))
print(soup.select('#list-2 .element.xxx'))
print(soup.select('#list-2')[0].select('.element'))
print(soup.select('#list-2 h1')[0].attrs)
print(soup.select('#list-2 h1')[0].get_text())
6. selenium
6.1 selenium 介绍
selenium最初是一个自动化测试工具,而爬虫中使用它主要是为了解决requests无法直接执行JavaScript代码的问题
selenium本质是通过驱动浏览器,完全模拟浏览器的操作,比如跳转、输入、点击、下拉等,来拿到网页渲染之后的结果,可支持多种浏览器
官方中文网站
from selenium import webdriver
browser=webdriver.Chrome()
browser=webdriver.Firefox()
browser=webdriver.PhantomJS()
browser=webdriver.Safari()
browser=webdriver.Edge()
6.2 安装
由于是驱动浏览器:需要确定好驱动哪个浏览器(ie,火狐,谷歌(推荐)),下载相应的驱动
-
安装: pip3 install selenium -
安装镜像站(以谷歌为例,谷歌国内访问不到使用镜像): 国内镜像网站地址:http://npm.taobao.org/mirrors/chromedriver/ 最新的版本去官网找: https://sites.google.com/a/chromium.org/chromedriver/downloads -
Firefox 需要安装 geckodriver:https://github.com/mozilla/geckodriver/releases
注意:安装的驱动要和谷歌浏览器适配,如下示例
- 查看浏览器版本
- 选择对应的驱动
- 根据自己的系统下载驱动
- 将其放置在项目根目录下,或者放到 python 安装路径的 scripts 目录中,便于找到即可
6.3 基本使用
from selenium import webdriver
import time
chrome = webdriver.Chrome()
chrome.get('http://www.baidu.com')
time.sleep(2)
chrome.close()
6.4 查找元素
通过ID查找元素
login_form = driver.find_element_by_id('loginForm')
from selenium import webdriver
from selenium.webdriver.common.by import By
chrome = webdriver.Chrome()
chrome.get('http://www.baidu.com')
res1 = chrome.find_element(By.ID, 'su')
print(res1)
chrome.close()
通过Name查找元素
...
username = chrome.find_element_by_name('username')
password = chrome.find_element_by_name('password')
from selenium.webdriver.common.by import By
username = chrome.find_element(By.NAME, 'username')
password = chrome.find_element(By.NAME, 'password')
...
通过XPath查找元素
XPath 可以参考菜鸟教程教学。最省力的方式就是复制
通过链接文本获取超链接
...
link = chrome.find_element_by_link_text('新闻')
link1 = chrome.find_element_by_partial_link_text('新')
link2 = chrome.find_element(By.LINK_TEXT, '新闻')
link3 = chrome.find_element(By.PARTIAL_LINK_TEXT, '新')
print(link == link1 == link2 == link3)
...
通过标签名查找元素
...
p = chrome.find_element_by_tag_name('p')
p1 = chrome.find_element(By.TAG_NAME, 'p')
print(p == p1)
...
通过Class name 定位元素
...
class1 = chrome.find_element_by_class_name('soutu-btn')
class2 = chrome.find_element(By.CLASS_NAME, 'soutu-btn')
print(class1 == class2)
...
通过CSS选择器查找元素
...
class1 = chrome.find_element_by_css_selector('.soutu-btn')
class2 = chrome.find_element(By.CSS_SELECTOR, '.soutu-btn')
print(class1 == class2)
...
6.5 等待页面加载完成
显式等待
显式等待是你在代码中定义等待一定条件发生后再进一步执行你的代码。 最糟糕的案例是使用time.sleep(),它将条件设置为等待一个确切的时间段。 这里有一些方便的方法让你只等待需要的时间。WebDriverWait结合ExpectedCondition 是实现的一种方式。
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
chrome = webdriver.Chrome()
chrome.get('http://www.baidu.com')
chrome.find_element_by_link_text('登录').click()
wait = WebDriverWait(chrome, 10)
element = wait.until(EC.element_to_be_clickable((By.ID, 'ss')))
print(element)
chrome.close()
配合异常处理
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
driver = webdriver.Firefox()
driver.get("http://somedomain/url_that_delays_loading")
try:
element = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.ID, "myDynamicElement"))
)
finally:
driver.quit()
在抛出TimeoutException异常之前将等待10秒或者在10秒内发现了查找的元素。 WebDriverWait 默认情况下会每500毫秒调用一次ExpectedCondition直到结果成功返回。 ExpectedCondition成功的返回结果是一个布尔类型的true或是不为null的返回值。
隐式等待
如果某些元素不是立即可用的,隐式等待是告诉WebDriver去等待一定的时间后去查找元素。 默认等待时间是0秒,一旦设置该值,隐式等待是设置该WebDriver的实例的生命周期。
...
chrome.implicitly_wait(10)
...
6.6 浏览器交互
点击
x.click()
chrome.maximize_window()
login = chrome.find_element(By.ID, 'login_btn')
chrome.execute_script("arguments[0].click();", login)
执行 JS 代码
代码执行 JS 语句使用 execute_script
...
chrome.execute_script('scrollTo(0,document.body.scrollHeight)')
chrome.execute_script('alert(123)')
...
获取位置、属性、大小和文本
from selenium import webdriver
from selenium.webdriver.common.by import By
import base64
chrome = webdriver.Chrome()
chrome.implicitly_wait(10)
chrome.get('http://www.baidu.com')
chrome.find_element_by_link_text('登录').click()
img = chrome.find_element(By.CLASS_NAME, 'tang-pass-qrcode-img')
location = img.location
size = img.size
print(img.id)
print(img.tag_name)
src = img.get_attribute('src')
b_src = base64.b64decode(src)
with open('code.png', 'wb') as f:
f.write(b_src)
chrome.close()
切换选项卡
from selenium import webdriver
chrome = webdriver.Chrome()
chrome.implicitly_wait(10)
chrome.get('http://www.baidu.com')
chrome.execute_script('window.open()')
chrome.switch_to.window(chrome.window_handles[1])
chrome.get('http://www.baidu.com')
chrome.switch_to.window(chrome.window_handles[0])
input()
chrome.close()
浏览器前进后退
from selenium import webdriver
import time
chrome = webdriver.Chrome()
chrome.implicitly_wait(10)
chrome.get('http://www.baidu.com')
chrome.get('https://www.cnblogs.com/liuqingzheng/')
chrome.back()
time.sleep(1)
chrome.forward()
chrome.quit()
异常处理
from selenium import webdriver
import time
chrome = webdriver.Chrome()
try:
chrome.implicitly_wait(10)
chrome.get('http://www.baidu.com')
chrome.get('https://www.cnblogs.com/liuqingzheng/')
chrome.back()
time.sleep(1)
chrome.forward()
except Exception as e:
print(e)
finally:
chrome.quit()
无界面浏览器
不显示的打开浏览器的图形化界面,且获取数据
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
chrome_options = Options()
chrome_options.add_argument('window-size=1920x3000')
chrome_options.add_argument('--disable-gpu')
chrome_options.add_argument('--hide-scrollbars')
chrome_options.add_argument('blink-settings=imagesEnabled=false')
chrome_options.add_argument('--headless')
chrome = webdriver.Chrome(options=chrome_options)
try:
chrome.implicitly_wait(10)
chrome.get('http://www.baidu.com')
print(chrome.page_source)
except Exception as e:
print(e)
finally:
chrome.quit()
模拟百度登录、搜索
需要给输入框写入内容,使用方法 send_keys
from selenium import webdriver
from selenium.webdriver.common.by import By
import time
chrome = webdriver.Chrome()
chrome.implicitly_wait(10)
chrome.get('http://www.baidu.com')
chrome.find_element_by_link_text('登录').click()
username = chrome.find_element(By.ID, 'TANGRAM__PSP_11__userName')
password = chrome.find_element(By.ID, 'TANGRAM__PSP_11__password')
login = chrome.find_element(By.ID, 'TANGRAM__PSP_11__submit')
username.send_keys('123')
time.sleep(1)
password.send_keys('123')
login.click()
chrome.close()
from selenium import webdriver
from selenium.webdriver.common.by import By
chrome = webdriver.Chrome()
try:
chrome.implicitly_wait(10)
chrome.get('http://www.baidu.com')
chrome.find_element_by_link_text('登录').click()
b_input = chrome.find_element(By.ID, 'kw')
seek = chrome.find_element(By.ID, 'su')
b_input.send_keys('妹子')
seek.click()
input()
except Exception as e:
print(e)
finally:
chrome.quit()
模拟博客园登录获取cookie
from selenium import webdriver
from selenium.webdriver.common.by import By
import json
chrome = webdriver.Chrome()
try:
chrome.implicitly_wait(10)
chrome.get('https://www.cnblogs.com/')
chrome.find_element_by_link_text('登录').click()
username = chrome.find_element(By.ID, 'mat-input-0')
password = chrome.find_element(By.ID, 'mat-input-1')
login = chrome.find_element(By.CSS_SELECTOR, 'button')
username.send_keys('123')
password.send_keys('123')
login.click()
cookie = chrome.get_cookies()
with open('a.json', 'w', encoding='utf-8') as f:
json.dump(cookie, f)
input()
except Exception as e:
print(e)
finally:
chrome.quit()
from selenium import webdriver
from selenium.webdriver.common.by import By
import json
import time
chrome = webdriver.Chrome()
try:
chrome.implicitly_wait(10)
chrome.get('https://www.cnblogs.com/')
with open('a.json', 'r', encoding='utf-8') as f:
res = json.load(f)
for i in res:
chrome.add_cookie(i)
chrome.refresh()
input()
except Exception as e:
print(e)
finally:
chrome.quit()
抽屉新热榜点赞
from selenium import webdriver
from selenium.webdriver.common.by import By
import json
import time
chrome = webdriver.Chrome()
try:
chrome.implicitly_wait(10)
chrome.get('https://dig.chouti.com/')
login = chrome.find_element(By.ID, 'login_btn')
chrome.execute_script("arguments[0].click();", login)
phone = chrome.find_element(By.NAME, 'phone')
password = chrome.find_element(By.NAME, 'password')
login1 = chrome.find_element(By.CLASS_NAME, 'login-btn')
phone.send_keys('123123123')
password.send_keys('123123123')
time.sleep(3)
cookie = chrome.get_cookies()
with open('b.json', 'w', encoding='utf-8') as f:
json.dump(cookie, f)
input()
except Exception as e:
print(e)
finally:
chrome.quit()
from selenium import webdriver
from selenium.webdriver.common.by import By
import json
import time
from bs4 import BeautifulSoup
import requests
from selenium.webdriver.chrome.options import Options
chrome_options = Options()
chrome_options.add_argument('window-size=1920x3000')
chrome_options.add_argument('--disable-gpu')
chrome_options.add_argument('--hide-scrollbars')
chrome_options.add_argument('blink-settings=imagesEnabled=false')
chrome_options.add_argument('--headless')
chrome = webdriver.Chrome(options=chrome_options)
try:
chrome.implicitly_wait(10)
chrome.get('https://dig.chouti.com/')
with open('b.json', 'r', encoding='utf-8') as f:
res = json.load(f)
cookies = {}
for i in res:
chrome.add_cookie(i)
cookies[i.get('name')] = i.get('value')
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36',
}
proxies = {
'http': '112.14.47.6:52024',
}
res = requests.get('https://dig.chouti.com/', headers=headers, proxies=proxies)
soup = BeautifulSoup(res.text, 'lxml')
soup_list = soup.find_all('div', class_='link-item')
id_list = []
for i in soup_list:
id_list.append(i.attrs['data-id'])
for i in id_list:
res1 = requests.post('https://dig.chouti.com/link/vote', data={'linkId': i}, headers=headers, proxies=proxies,
cookies=cookies)
print(i)
chrome.refresh()
input()
except Exception as e:
print(e)
finally:
chrome.quit()
其他案例
import requests
header = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36'
}
data = {
'cname': '',
'pid': 20,
'keyword': '浦东',
'pageIndex': 1,
'pageSize': 10
}
ret = requests.post('http://www.kfc.com.cn/kfccda/ashx/GetStoreList.ashx?op=keyword', data=data, headers=header)
print(ret.json())
7. 动作链
用 selenium 做自动化,需要模拟鼠标操作才能进行的情况,例如校验验证码的单击、双击、点击鼠标右键、拖拽等等。可以使用 selenium 提供了 ActionChains 类来处理
如果被检测出浏览器被自动化测试软件控制,可以添加 stealth.min.js 文件,如下所示。
options = Options()
options.add_argument("--disable-blink-features=AutomationControlled")
bro = webdriver.Chrome(options=options)
with open('../stealth.min.js', 'r', encoding='utf8') as f:
res = f.read()
chrome.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
"source": res
})
导入语句:from selenium.webdriver import ActionChains
方法 | 说明 |
---|
click(on_element=None) | 单击鼠标左键 | click_and_hold(on_element=None) | 点击鼠标左键,不松开 | context_click(on_element=None) | 点击鼠标右键 | double_click(on_element=None) | 双击鼠标左键 | drag_and_drop(source, target) | 拖拽到某个元素然后松开 | drag_and_drop_by_offset(source, xoffset, yoffset) | 拖拽到某个坐标然后松开 | key_down(value, element=None) | 按下某个键盘上的键 | key_up(value, element=None) | 松开某个键 | move_by_offset(xoffset, yoffset) | 鼠标从当前位置移动到某个坐标 | move_to_element(to_element) | 鼠标移动到某个元素 | move_to_element_with_offset(to_element, xoffset, yoffset) | 移动到距某个元素(左上角坐标)多少距离的位置 | perform() | 执行链中的所有动作 | release(on_element=None) | 在某个元素位置松开鼠标左键 | send_keys(*keys_to_send) | 发送某个键到当前焦点的元素 | send_keys_to_element(element, *keys_to_send) | 发送某个键到指定元素 |
7.1 基础使用
from selenium import webdriver
from selenium.webdriver import ActionChains
from selenium.webdriver.common.by import By
chrome = webdriver.Chrome()
chrome.implicitly_wait(10)
chrome.maximize_window()
try:
chrome.get('http://www.runoob.com/try/try.php?filename=jqueryui-api-droppable')
chrome.switch_to.frame('iframeResult')
sourse = chrome.find_element(By.ID, 'draggable')
target = chrome.find_element(By.ID, 'droppable')
action = ActionChains(chrome)
action.drag_and_drop(sourse, target)
action.perform()
input()
except Exception as e:
print(e)
finally:
chrome.quit()
方法一:将动作放在同一个动作链中
from selenium import webdriver
from selenium.webdriver import ActionChains
from selenium.webdriver.common.by import By
chrome = webdriver.Chrome()
chrome.implicitly_wait(10)
chrome.maximize_window()
try:
chrome.get('http://www.runoob.com/try/try.php?filename=jqueryui-api-droppable')
chrome.switch_to.frame('iframeResult')
sourse = chrome.find_element(By.ID, 'draggable')
target = chrome.find_element(By.ID, 'droppable')
action = ActionChains(chrome)
action.click_and_hold(sourse)
distance = target.location['x'] - sourse.location['x']
track = 0
while track < distance:
action.move_by_offset(xoffset=15, yoffset=0)
track += 15
action.perform()
except Exception as e:
print(e)
finally:
chrome.quit()
方法二:把动作放在不同的动作链
from selenium import webdriver
from selenium.webdriver import ActionChains
from selenium.webdriver.common.by import By
chrome = webdriver.Chrome()
chrome.implicitly_wait(10)
chrome.maximize_window()
try:
chrome.get('http://www.runoob.com/try/try.php?filename=jqueryui-api-droppable')
chrome.switch_to.frame('iframeResult')
sourse = chrome.find_element(By.ID, 'draggable')
target = chrome.find_element(By.ID, 'droppable')
action = ActionChains(chrome)
action.click_and_hold(sourse).perform()
distance = target.location['x'] - sourse.location['x']
track = 0
while track < distance:
ActionChains(chrome).move_by_offset(xoffset=15, yoffset=0).perform()
track += 15
except Exception as e:
print(e)
finally:
chrome.quit()
7.2 12306 登录
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver import ActionChains
import time
import json
chrome = webdriver.Chrome(executable_path='../chromedriver.exe')
chrome.implicitly_wait(10)
chrome.maximize_window()
with open('../stealth.min.js', 'r', encoding='utf8') as f:
res = f.read()
chrome.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
"source": res
})
try:
chrome.get('https://www.12306.cn/index/')
login = chrome.find_element(By.ID, 'J-btn-login')
login.click()
username = chrome.find_element(By.ID, 'J-userName')
password = chrome.find_element(By.ID, 'J-password')
username.send_keys('123123')
password.send_keys('123123')
login_btn = chrome.find_element(By.ID, 'J-login')
login_btn.click()
action = ActionChains(chrome)
sliding_block = chrome.find_element(By.ID, 'nc_1_n1z')
action.click_and_hold(sliding_block)
track = 0
while track < 300:
action.move_by_offset(xoffset=30, yoffset=0)
track += 30
action.perform()
time.sleep(2)
cookie = chrome.get_cookies()
with open('12306.json', 'w', encoding='utf-8') as f:
json.dump(cookie, f)
except Exception as e:
print(e)
finally:
chrome.quit()
|