第1章 初识爬虫

1.1 第一个爬虫程序

1
2
3
4
5
6
7
8
9
10
11
12
13
# 爬虫:从互联网获取所需资源
# 需求:用程序模拟浏览器,输入一个网址,从该网址中获取到资源或内容
from urllib.request import urlopen

url = "http://www.baidu.com" # 网址变量
resp = urlopen(url)

# print(resp.read().decode("utf-8"))

with open("mybaidu.html", mode="w") as f:
f.write(resp.read().decode("utf-8")) # 读取网页源代码并保存为文件
print("over")

1.2 web请求过程剖析

  • 服务器渲染:在服务器端直接把数据和html整合在一起,统一返回给浏览器——在源代码中有数据
  • 客户端渲染:第一次请求只拿到html骨架。第二次请求拿到数据,进行数据展示——源代码中没数据
  • 熟练使用浏览器抓包工具

1.3 HTTP协议

协议

就是两个计算机之间为了能够流畅的进行沟通而设置的一个君子协定。常见的有TCP/IP.SOAP协议,HTTP协议、SMTP协议等

HTTP协议:

(Hyper Text Transfer Protocol)指超文本传输协议,是用于从万维网(www:,world wide web)服务器传输超文本到本地浏览器的传送协议,直白点,是浏览器和服务器之间的数据交互遵守的协议

HTTP协议把一条消息分为三大块消息(无论是请求还是响应)

请求

1
2
3
4
5
6
请求行 -> 请求方式(Request Method)(get/post) 请求URL地址 协议
请求头(Request Headers) -> 放一些服务器使用的附加信息
User-Agent:请求载体的身份标识(用啥发送的请求)
Referer:防盗链(这次请求是从哪个页面来的)(反爬会用)
Cookie:本地字符串数据信息(用户登录信息、反爬的token)
请求体 -> 一般放一些请求参数

响应

1
2
3
4
5
状态行 -> 协议 状态码(Status Code)(404等)
响应头(Response Headers) -> 放一些客户端要使用的一些附加信息
cookie:本地字符串数据信息(用户登录信息、反爬的token)
各种神奇的莫名其妙的字符串(依赖经验,一般都是token字样,防止各种攻击和反爬)
响应体 -> 服务器返回的真正客户端要用的内容(HTML、json)等

请求方式

GET:显示提交

POST:隐式提交

1.4 requests入门01

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 安装requests
# pip install requests
# 网速较慢时,国内镜像(清华源)
# pip install -i https://pypi.tuna.tsinghua.edu.cn/simple requests

import requests
query = input("请输入你要搜索的一个关键词:")
url = f'http://www.sogou.com/web?query={query}'

header = {
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36"
}
resp = requests.get(url, headers=header) # 处理一个小反爬:说明自己是浏览器

print(resp) # <Response [200]>
print(resp.text) # 拿到页面源代码 -> 用户您好,我们的系统检测到您网络中存在异常访问请求。

1.5 requests入门02

1
2
3
4
5
6
7
8
9
10
11
12
import requests

url = "https://fanyi.baidu.com/sug"
s = input("请输入你要翻译的英文单词:")
dat = {
"kw": s # Payload中的From Data
}
# 发送post请求,发送的数据必须放在字典中,通过data参数进行传递
resp = requests.post(url, data=dat)

print(resp.json()) # 将服务器返回的内容直接处理成json() => dict

1.6 requests入门03

XHR:(XMLHttpRqquest)

URL:'?'前面为网址,后面为参数(可以在Query String Parameters中看到)

image-20220812215805500

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import requests

# url = "https://movie.douban.com/j/chart/top_list?type=24&interval_id=100%3A90&action=&start=0&limit=20"
url = "https://movie.douban.com/j/chart/top_list"
# url过长
# 重新封装参数
param = {
"type": "24",
"interval_id": "100:90",
"action": "",
"start": 0, # 排名起始段
"limit": 20
}
# 后补充headers
header = {
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36"
}

resp = requests.get(url=url, params=param,headers=header)

print(resp.request.url)
# 获得https://movie.douban.com/j/chart/top_list?type=24&interval_id=100%3A90&action=&start=0&limit=20

print(resp.text)
# 无headers时无反应:说明被反爬,先看User-Agent

# 默认User-Agent(不设置headers时测试)
print(resp.request.headers)
# {'User-Agent': 'python-requests/2.28.1', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'Connection': 'keep-alive'}

print(resp.json())

1.7 补充

  • 关掉resp, 避免请求次数过多,使用close方法关闭
1
2
3
4
import requests
url = "https://movie.douban.com"
resp = requests.get(url=url)
resp.close()
  • open的编码问题: mac默认utf-8, win为gbk
1
open("xxx.txt", mode="w", encoding = "gbk/utf-8")

第2章 数据解析与提取

2.1 数据解析概述

三种解析方式:

  1. re解析
  2. bs4解析(性能慢)
  3. xpath解析

​ 三种方式可以混合使用,完全以结果做导向,只要能拿到想要的数据,用什么方案不重要,当掌握了这些之后再考虑性能问题

2.2 正则表达式

Regular Expression:

正则表达式,一种使用表达式方式对字符串进行匹配的语法规则

我们抓取到的网页源代码本质上是一个超长的字符串,想从里面提取内容,用正则再合适不过了

正则优点:速度快、效率高、准确性高

正则缺点:新手难度高

正则语法:使用元字符进行排列组合用来匹配字符串在线正则表达式测试

元字符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
.		匹配除换行符以外的任意字符
\w 匹配字母数字下划线
\d 匹配数字
\n 匹配换行符
\t 匹配制表符

^ 匹配字符串的开始
$ 匹配字符串的结尾

\W 匹配非字母数字下划线
\D 匹配非数字
\S 匹配非空白符号
a|b 匹配字符a或字符b
() 匹配括号内的表达式,也表示一个组
[...] 匹配字符组中字符
[^...] 匹配除了字符组中的字符

量词:控制前面的元字符出现的次数

1
2
3
4
5
6
*		重复零次或更多次
+ 重复一次或更多次
? 重复一次或多次(出现或不出现)
{n} 至少匹配n次,n为非负整数
{n,m} 最少匹配n次且最多匹配m次
{n,} 重复n次或更多次

image-20220814214842249

贪婪匹配和惰性匹配

1
2
.*		贪婪匹配(多个匹配选最长)
.*? 惰性匹配(多个匹配选最短)

image-20220814215312211

image-20220814215333031

image-20220814220815226

2.3 re模块

python中使用正则 -> re模块

re模块常见功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import re
# findall(正则, 字符串, flag):匹配字符串中所有符合正则的内容(返回列表)
lst = re.findall(r"\d+", "我的电话是:10086")
print("findall:", lst)

# finditer:匹配字符串中所有的内容(返回迭代器,迭代器中拿到内容使用group())
it = re.finditer(r"\d+", "我的电话是:10086,我女朋友的电话是10010")
print("finditer:")
for each in it:
print(each.group())

# search找到第一个结果返回match对象,那数据使用group()
s = re.search(r"\d+", "我的电话是:10086,我女朋友的电话是10010")
print("search:", s.group())
# match从头开始匹配
s = re.match(r"\d+", "10086,我女朋友的电话是10010")
print("match:", s.group())

# 预加载正则表达式(compile())
obj = re.compile(r"\d+")
ret = obj.finditer("我的电话号码是:10086,我女朋友的电话是:10010")
print("预加载:")
for it in ret:
print(it.group())
ret = obj.findall("我的电话号码是:10086,我女朋友的电话是:10010")
print(ret)

s = """
<div class='lzh'><span id='1'>刘征昊</span></div>
<div class='czy'><span id='2'>陈石榴</span></div>
<div class='wyz'><span id='3'>王尖尖</span></div>
<div class='xxq'><span id='4'>kesiu</span></div>
<div class='wyx'><span id='5'>王冲冲</span></div>
"""
# re.S可以使'.'匹配换行符
obj = re.compile(r"<div class='.*?'><span id='\d+'>.*?</span></div>", re.S)
result = obj.finditer(s)
for it in result:
print(it.group())
# (?P<xxx>正则)可以从正则字符串中进一步提取内容并存在xxx里
obj = re.compile(r"<div class='.*?'><span id='\d+'>(?P<name>.*?)</span></div>", re.S)
result = obj.finditer(s)
for it in result:
print(it.group("name"))

2.4 re实例

2.4.1 手刃豆瓣TOP250

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# 拿到页面源代码   requests
# 通过re来提取想要的有效信息 re
import requests
import re
import csv
import os

start = 0
header = {
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36"
}
os.remove("data.csv") # 删除旧文件
f = open("data.csv", "a")

while start <= 225:
url = f"https://movie.douban.com/top250?start={start}&filter="
start += 25
resp = requests.get(url, headers=header)
page_content = resp.text

# 解析数据
obj = re.compile(r'<li>.*?<div class="item">.*?<span class="title">(?P<name>.*?)'
r'</span>.*?<p class="">.*?<br>(?P<year>.*?)&nbsp.*?'
r'<span class="rating_num" property="v:average">(?P<score>.*?)</span>'
r'.*?<span>(?P<num>.*?)人评价</span>', re.S)

# <li>.*?<div class="item">为一体判断,中间用于过滤换行符等
# 开始匹配
result = obj.finditer(page_content)
csvWriter = csv.writer(f)

for it in result:
# print(it.group("name"))
# print(it.group("year").strip()) # 处理空格
# print(it.group("score"))
# print(it.group("num"))
dic = it.groupdict()
dic["year"] = dic["year"].strip()
csvWriter.writerow(dic.values())
f.close()
print("over!")

2.4.2 屠戮电影天堂

html中a标签表示超链接

1
<a href='xxx', title='yyy'>zzz</a>

链接地址(href):xxx 链接预览(title):yyy 链接名称:zzz

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# 1.定位到2020必看片(2022必看热片)
# 2.从中提取子页面的链接地址
# 3.请求子页面链接地址,拿到我们想要的下载地址
import requests
import re

domain = "https://www.dytt89.com/"
resp = requests.get(domain, verify=False)
resp.encoding = 'gb2312' # 指定字符集
# verify=False去掉安全验证
# print(resp.text)

# 拿到ul里面的li
obj1 = re.compile(r"2022必看热片.*?<ul>(?P<ul>.*?)</ul>", re.S)
obj2 = re.compile(r"<a href='(?P<href>.*?)'", re.S)
obj3 = re.compile(r'◎片  名(?P<movie>.*?)<br />.*?<td style="WORD-WRAP: '
r'break-word" bgcolor="#fdfddf"><a href="(?P<download>.*?)">', re.S)
result1 = obj1.finditer(resp.text)
sub_href_lst = []
for it in result1:
ul = it.group('ul')
# print(ul)
# 提取子页面链接
result2 = obj2.finditer(ul)
for itt in result2:
# print(itt.group('href'))
# 拼接子页面url:域名+子页面地址
sub_href = domain + itt.group('href').strip("/")
# 子页面列表存储
sub_href_lst.append(sub_href)
# print(sub_href)
# 提取子页面内容
for href in sub_href_lst:
sub_resp = requests.get(href, verify=False)
sub_resp.encoding = 'gb2312'
# print(sub_resp.text)
result3 = obj3.search(sub_resp.text)
print(result3.group("movie").strip())
print(result3.group("download"))
# break # 测试用

2.5 bs4解析_HTML语法

HTML是超文本标记语言,是我们编写网页的最基本也是最核心的一种语言,其语法规则就是用不同的标签对网页上的内容进行标记,从而使网页显示出不同的效果

1
2
3
4
5
6
<h1>
i love you
</h1>
<h2 align="center">
i don't love you
</h2>

h1:标签
align:属性
center:属性值–>
==<标签 属性=“属性值”>被标记内容</标签>==

1
2
3
<img src="xxx.jpg"/>
<br />
<a href="http://www.baidu.com">周杰伦 </a>

自封闭标签⬆️

1
2
3
4
5
<div id="1" class="h1">周杰伦</div>
<div id="2" class="h1">林俊杰</div>
<div id="3" class="h1">马化腾</div>
<div id="4" class="h4">唐老鸭</div>
<div id="5" class="h5">刘征昊</div>

通过标签名称/特征来拿到数据

div -> id:3 =>马化腾

div -> class:h4 =>唐老鸭

2.6 bs4解析_新发地

安装:pip install bs4 -i

1.拿到页面源代码

2.使用bs4解析源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import requests
import csv
from bs4 import BeautifulSoup

url = "http://www.xinfadi.com.cn/index.html"
resp = requests.get(url)
# print(resp.text)
f = open("新发地.csv", "w")
csv_writer = csv.writer(f)
# 解析数据
# 1.把页面源代码交给BeaytifulSoup进行处理,生成bs4对象
page = BeautifulSoup(resp.text, "html.parser") # 指定html解析器
# 2.从bs对象中查找数据
# find(标签, 属性=值)
# find_all
# table = page.find("table", class_="hq_table") # class是python关键字,加下划线区分
table = page.find("table", attrs={"border": "0", "cellspacing": "0", "cellpadding": "0"}) # 使用字典避免上面的情况
print(table)
trs = table.find_all("tr")
for tr in trs:
ths = tr.find_all("th")
for i in range(len(ths)):
# print(th.text) 获取被标签的内容
ths[i] = ths[i].text
csv_writer.writerow(ths)

2.7 bs4解析_抓取优美图库

1.拿到主页面的源代码,然后提取子页面的链接地址,href

2.通过href拿到子页面的内容,从子页面中找到图片的下载地址 img->src

3.下载图片(二进制文件直接写入)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import requests
from bs4 import BeautifulSoup
import time
url = "https://www.umei.cc/bizhitupian/weimeibizhi/"

resp = requests.get(url)
resp.encoding = "utf-8"
# print(resp.text)

# 把源代码交给bs
main_page = BeautifulSoup(resp.text, "html.parser")
a_lst = main_page.find("div", attrs={"class": "swiper-box"}).find_all("a")
# print(a_lst)
for a in a_lst:
# .get()可以直接拿到属性的值
href = "https://www.umei.cc"+a.get('href')
# 拿到子页面的源代码
sub_page_resp = requests.get(href)
sub_page_resp.encoding = "utf-8"
sub_page_text = sub_page_resp.text
# 从子页面中拿到图片的下载地址
sub_page = BeautifulSoup(sub_page_text, "html.parser")
section = sub_page.find("section", attrs={"class": "img-content"})
img = section.find("img")
src = img.get("src")
# 下载图片
img_resp = requests.get(src)
# img_resp.content #拿到图片字节
img_name = src.split('/')[-1] # 拿到url中最后一个/后内容作为名字
with open("2.7img/"+img_name, mode="wb") as f:
f.write(img_resp.content)
print("over!", img_name)
time.sleep(1)

print("all over!")

2.8 xpath解析

2.8.1 xpath解析入门1

xpathXML文档中搜索内容的一种语言

html是xml的一个子集

1
2
3
4
5
6
7
8
9
<book>
<id>1</id>
<name>野花遍地香</name>
<price>1.23</price>
<author>
<nick>周大强</nick>
<nick>周芷若</nick>
</author>
</book>

称作节点

book与id、name等互为父子节点;id name为兄弟节点

访问规则:/book/price(类似文件路径) 也可通过属性

安装lxml模块链接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# xpath解析
from lxml import etree

xml = """
<book>
<id>1</id>
<name>野花遍地香</name>
<price>1.23</price>
<nick>臭豆腐</nick>
<author>
<nick id="10086">周大强</nick>
<nick id="10010">周芷若</nick>
<nick class="jay">周杰伦</nick>
<nick class="jolin">蔡依林</nick>
<div>
<nick>热热热热1</nick>
</div>
<span>
<nick>热热热热2</nick>
</span>
</author>

<partner>
<nick id="ppc">胖胖沉</nick>
<nick id="ppbc">胖胖不沉</nick>
</partner>
</book>
"""

tree = etree.XML(xml)
# result = tree.xpath("/book") 拿到节点
# result = tree.xpath("/book/name/text()") # /text()拿到节点内文本
# result = tree.xpath("/book/author/nick/text()") # 只会找同层级nick
# result = tree.xpath("/book/author//nick/text()") # 找不同层级nick(包含后代)
# result = tree.xpath("/book/author/*/nick/text()") # *为任意节点,通配符
result = tree.xpath("/book//nick/text()") # *为任意节点,通配符
print(result)

2.8.1 xpath解析入门2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from lxml import etree

tree = etree.parse("2.8.2xpath.html")
# result = tree.xpath('/html')
# result = tree.xpath('/html/body/ul/li/a/text()')
# result = tree.xpath('/html/body/ul/li[1]/a/text()') # xpath下标从1开始
# result = tree.xpath("/html/body/ol/li/a[@href='dapao']/text()") # [@xxx="yyy]指定属性及值
ol_li_lst = tree.xpath("/html/body/ol/li")
for li in ol_li_lst:
# 从每个li中提取文字
result = li.xpath("./a/text()") # 子节点继续查找(相对查找)
print(result)
result2 = li.xpath("./a/@href") # /@xxx相对查找属性值
print(result2)

print(tree.xpath("/html/body/ul/li/a/@href"))

print(tree.xpath("/html/body/div[1]/text()")) # 使用检查定位到代码段,右键复制代码段xpath

2.9 xpath解析实例_干猪八戒

步骤

  1. 拿到页面源代码
  2. 提取和解析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import requests
from lxml import etree

url = "https://huhehaote.zbj.com/search/service/?kw=saas&r=1"
resp = requests.get(url)
# print(resp.text)

# 解析
html = etree.HTML(resp.text)
# 拿到每一个服务商的div
divs = html.xpath('/html/body/div[@id="__nuxt"]/div/div/div[3]/div/div[3]/div[4]/div[1]/div')
for div in divs:
# print(div)
price = div.xpath("./div[3]/div[1]/span/text()")[-1].strip("¥")
title = div.xpath("./div[3]/a/text()")[0]
company_name = div.xpath("./a/div[2]/div[1]/div/text()")[0]
print(price)
print(title)
print(company_name)

第3章 requests模块进阶

3.1 requests进阶概述

headers是HTTP协议中的请求头,一般存放一些和请求内容无关的数据,有时也会存放一些安全验证信息,比如常见的:User-Agent,token,cookie等

通过 requests发送的请求,我们可以把请求头信息放在headers中,也可以单独进行存放,最终由requests自动帮我们拼接完成完整的http请求头。

本章内容

  1. 模拟浏览器登录->处理cookie
  2. 防盗链处理->抓取梨视频数据
  3. 代理->防止被封ip
  4. 综合训练:抓取网易云评论信息

3.2 模拟用户登录_处理cookie

登陆 -> 得到cookie,必须带着cookie去请求书架url -> 书架上得内容

**解决方案:**可以使用session(会话)进行请求 -> session可以认为是一连串的请求,在这个过程中cookie不会丢失

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import requests
# 会话
session = requests.session()
data = {
"loginName": "19997617865",
"password": "thebest1"
}
# 1.登陆
url = "https://passport.17k.com/ck/user/login"
resp1 = session.post(url, data=data)
# print(resp.text)
# print(resp.cookies)

# 2.拿书架上数据
# 刚才session中含有cookie
resp2 = session.get("https://user.17k.com/ck/author/shelf?page=1&appKey=2406394919")
print(resp2.json())

3.3 防盗链处理_抓取梨视频

无法打开的:https://video.pearvideo.com/mp4/adshort/20210122/ ==1660915088310==-15583450_adpkg-ad_hd.mp4

可以打开的:https://video.pearvideo.com/mp4/adshort/20210122/ ==cont-1717518==-15583450_adpkg-ad_hd.mp4

1660915088310 -> cont-1717518进行了替换

1.拿到cont ID

2.拿到videostatus返回的json -> srcURL

3.srcURL内容进行修改

4.下载视频

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import requests

url = "https://www.pearvideo.com/video_1717518"
headers = {
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36",
# 防盗链:溯源,当前本次请求的上一级是谁
"Referer": url
}
cont_id = url.split("_")[-1]

video_status = f"https://www.pearvideo.com/videoStatus.jsp?contId={cont_id}&mrd=0.24171138432822437"
resp = requests.get(video_status, headers=headers)
# print(resp.text) # 该文章已经下线!-> 被反爬 -> headers -> 仍然不行
src_url = resp.json()['videoInfo']['videos']['srcUrl']
system_time = resp.json()['systemTime']

src_url = src_url.replace(system_time, f"cont-{cont_id}")
print(src_url) # https://video.pearvideo.com/mp4/adshort/20210122/cont-1717518-15583450_adpkg-ad_hd.mp4
# 下载视频
with open("vedio.mp4", mode="wb") as f:
f.write(requests.get(src_url).content)
f.close()

3.4 代理

原理:通过第三方的一个机器去发送请求

1
2
3
4
5
6
7
8
import requests
# 218.60.8.83:3129
proxies = {
"https": "https://218.60.8.83:3129"
}
resp = requests.get("https://www.baidu.com", proxies=proxies)
resp.encoding = "utf-8"
print(resp.text)

3.5 综合训练_抓取网易云音乐热评

1.找到未加密的参数

2.想办法把参数进行加密(参考网易逻辑), params -> encText / encSecKey(加密函数:window.arsea(参数, xxx…))

3.请求到网易,拿到评论信息

AES加密:需要安装pycrypto -> pip install pycryptodome

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
from Crypto.Cipher import AES
from base64 import b64encode
import requests
import json
import csv

url = "https://music.163.com/weapi/comment/resource/comments/get?csrf_token="
# 请求方式:post
data = {
"csrf_token": "",
"cursor": "-1",
"offset": "0",
"orderType": "1",
"pageNo": "1",
"pageSize": "20",
"rid": "R_SO_4_1327162577",
"threadId": "R_SO_4_1327162577"
}
# 服务于d
e = "010001"
f = "00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7"
g = "0CoJUm6Qyw8W8jud"
i = "3CHtjTJeAW2lKHMy" # 手动固定


def get_encSecKey(): # 基于i而固定的
return "de70557c99d4da3c4dba685119fcd3f6b5bf2f3561c659e53f94ab691d60408944d95d00d882b586429236fc882895060b4aba652eef62071323a4f1bee911d2be7146f21a741dc3b3335c0df1d0949c3c484a845f5b8c97a404c156647157116acd5301bbab9607a08d85d764dc3cb8bf519e97f87a81ee3d64ed3e0c9f0e83"


def get_params(d): # 默认收到字符串
first = enc_params(d, g)
second = enc_params(first, i)
return second # 返回的就是params


def to_16(d): # 转化为16的倍数,为AES算法服务
pad = 16 - len(d) % 16
d += chr(pad) * pad
return d


def enc_params(d, key): # 加密过程(还原b)
iv = "0102030405060708"
d = to_16(d)
aes = AES.new(key=key.encode("utf-8"), IV=iv.encode("utf-8"), mode=AES.MODE_CBC) # 创造加密器
bs = aes.encrypt(d.encode("utf-8")) # 加密,加密的内容长度必须是16倍数,
return str(b64encode(bs), "utf-8") # 转化成字符串

加密源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
 function a(a) {  //返回随机的16位字符串
var d, e, b = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", c = "";
for (d = 0; a > d; d += 1) ////循环16次
e = Math.random() * b.length, //随机数
e = Math.floor(e), //取整
c += b.charAt(e); //取b字符串中的xxx位置
return c
}
function b(a, b) {
var c = CryptoJS.enc.Utf8.parse(b) ////加密的密钥
, d = CryptoJS.enc.Utf8.parse("0102030405060708")
, e = CryptoJS.enc.Utf8.parse(a) //数据
, f = CryptoJS.AES.encrypt(e, c, {
iv: d, //AES加密:偏移量
mode: CryptoJS.mode.CBC //模式:cbc
});
return f.toString()
}
function c(a, b, c) { //c不产生随机数
var d, e;
return setMaxDigits(131),
d = new RSAKeyPair(b,"",c),
e = encryptedString(d, a)
}
function d(d, e, f, g) { //d:数据data e:执行后为010001 f/g:固定值如上
var h = {}
, i = a(16); //是一个16位随机字符串
return h.encText = b(d, g),
h.encText = b(h.encText, i), //返回的params, enctext通过b进行了两次加密
h.encSecKey = c(i, e, f), //得到encSecKey,且只与i有关
h //逗号语句,先执行前几个,然后return最后一个
}

加密后请求:

1
2
3
4
resp = requests.post(url, data={
"params": get_params(json.dumps(data)),
"encSecKey": get_encSecKey(),
})

评论数据提取及输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
f = open("comment.csv", mode="w")
f_hot = open("comment_hot.csv", mode="w")
csv_writer = csv.writer(f)
csv_writer_hot = csv.writer(f_hot)
# 提取最新评论
users = resp.json()["data"]["comments"]
for user in users:
nickname = user["user"]["nickname"]
content = user["content"]
timeStr = user["timeStr"]
comment = [nickname, content, timeStr]
csv_writer.writerow(comment)
# 提取热门评论
users_hot = resp.json()["data"]["hotComments"]
for user_hot in users_hot:
nickname = user_hot["user"]["nickname"]
content = user_hot["content"]
timeStr = user_hot["timeStr"]
comment_hot = [nickname, content, timeStr]
csv_writer_hot.writerow(comment_hot)

第4章 多线程与多进程

4.1 线程与进程

进程:资源单位,每个进程至少含有一个线程
线程:执行单位
(启动每一个程序都会默认有一个主线程)

4.2 多线程

4.2.1 单线程

1
2
3
4
5
6
7
def func():
for i in range(1000):
print("func", i)
if __name__ == '__main__':
func()
for i in range(1000):
print("main", i)

4.2.2 多线程1

1
2
3
4
5
6
7
8
9
from threading import Thread
def func():
for i in range(1000):
print("func", i)
if __name__ == '__main__':
t = Thread(target=func, args=("一号",)) # 创建线程并安排线程任务
t.start() # 多线程状态为可以开始(不意味即刻开始,具体时间由cpu决定)
for i in range(1000):
print("main", i)

4.2.3 多线程2

1
2
3
4
5
6
7
8
9
10
from threading import Thread
class myThread(Thread):
def run(self): # 固定的 -> 当线程被执行时启动
for i in range(1000):
print("子线程", i)
if __name__ == '__main__':
t = myThread()
t.start() # 开启线程
for i in range(1000):
print("主线程", i)

4.4 线程池和进程池

线程池:一次性开辟一些线程,用户直接给线程池提交任务,线程任务的调度交给线程池来完成

1
2
3
4
5
6
7
8
9
10
11
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
def fn(name):
for i in range(1000):
print(name, i)
if __name__ == "__main__":
# 创建线程池
with ThreadPoolExecutor(50) as t:
for i in range(100):
t.submit(fn, name=f"线程{i}")
# 等待线程池中的任务全部执行完毕,才继续执行
print("over")

4.5 线程池和进程池实例_新发地菜价

1.如何提取单个页面的数据

2.上线程池,多个页面同时抓取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import requests
import csv
from concurrent.futures import ThreadPoolExecutor

url = "http://www.xinfadi.com.cn/getPriceData.html"
f = open("菜价.csv", "w")
csvWriter = csv.writer(f)
firstLine = ['prodName', 'lowPrice', 'highPrice', 'avgPrice', 'unitInfo', 'place', 'pubDate']
csvWriter.writerow(firstLine)

def download_one_page(current):
# 拿到页面源代码
data = {
"current": str(current) # 传参:改变页数
}
resp = requests.post(url, data=data)
lsts = resp.json()['list']
for dic in lsts: # 获取json并选择有效数据
lst = [dic['prodName'], dic['lowPrice'], dic['highPrice'], dic['avgPrice'],
dic['unitInfo'], dic['place'], dic['pubDate'].split()[0]]
csvWriter.writerow(lst)

if __name__ == "__main__":
# for i in range(1,14870): # 效率低下
with ThreadPoolExecutor(50) as t:
for i in range(1, 201): # 开启线程池(50),爬取前200页
t.submit(download_one_page, i) # 把任务交给线程池
print("over!")
f.close()

4.6 协程

协程:当程序遇到了IO操作,可以选择性的切换到其他任务上。

微观上一个任务一个任务的切换,切换条件为IO操作;宏观上,可以观察到多个任务同时进行。(==多任务异步操==作)

条件:单线程

1
2
3
4
5
6
7
8
9
10
import time
def func():
print("我爱")
time.sleep(3)
# 让当前线程处于阻塞状态,cpu不工作
print("我真的爱")
# input()也处于阻塞状态
# requests.get(b站) 网络请求返回数据之前,程序也是阻塞状态
if __name__ == "__main__":
func()

py编写协程的程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import asyncio
import time
async def func1():
print("hello!")
# time.sleep(2) # 当程序出现了同步操作时,异步就中断了
await asyncio.sleep(2) # 异步操作实现方式
print("byebye")
async def func2():
print("我叫lzh")
# time.sleep(3)
await asyncio.sleep(3)
print("你好啊")
async def func3():
print("我叫lst")
# time.sleep(3)
await asyncio.sleep(4)
print("你好美啊")
if __name__ == "__main__":
# g = func1() # 此时函数是异步协程函数,此时函数执行得到一个协程对象
# asyncio.run(g) # 协程程序运行需要asynio模块支持
f1 = func1()
f2 = func2()
f3 = func3()
tasks = [f1, f2, f3]
# 一次性启动多个任务(协程)
t1 = time.time()
asyncio.run(asyncio.wait(tasks))
t2 = time.time()
print(t2 - t1)

出现同步操作时的异步操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
async def func1():
print("hello!")
# time.sleep(2) # 当程序出现了同步操作时,异步就中断了
await asyncio.sleep(2) # 异步操作实现方式
print("byebye")

async def func2():
print("我叫lzh")
# time.sleep(3)
await asyncio.sleep(3)
print("你好啊")


async def func3():
print("我叫lst")
# time.sleep(3)
await asyncio.sleep(4)
print("你好美啊")


async def main():
# 写法1
# f1 = func1() 一般await挂起操作在协程对象前面
# await f1
# 写法2(推荐)
tasks = [asyncio.create_task(func1()), asyncio.create_task(func2()), asyncio.create_task(func3())]
await asyncio.wait(tasks)

if __name__ == "__main__":
t1 = time.time()
asyncio.run(main())
t2 = time.time()
print(t2 - t1)

在爬虫领域的应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
async def download(url):
print("准备开始下载")
await asyncio.sleep(2) # 网络请求:requests.get()
print("下载完成")


async def main():
urls = [
"http://www.baidu.com",
"http://www.google.com",
"http://www.163.com"
]
# 准备异步协程对象列表
tasks = []
for url in urls:
tasks.append(asyncio.create_task(download(url)))
# tasks = [asyncio.create_task(download(url)) for url in urls]
# 一次性所有任务都执行
await asyncio.wait(tasks)
if __name__ == "__main__":
b_id = "4306063500"
url = "www"
asyncio.run(download, url)

4.7 异步http请求_aiohttp模块讲解

requests.get()同步的代码 -> 异步操作aiohttp

安装:pip install aiohttp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import asyncio
import aiohttp

urls = [
"http://kr.shanghai-jiuxin.com/file/2022/0812/59a67a6e9e44225285d4a545cd2acb0e.jpg",
"http://kr.shanghai-jiuxin.com/file/2022/0811/94294a360b48155f87bf1e25fe6c247a.jpg",
"http://kr.shanghai-jiuxin.com/file/2022/0812/36bc4b3ea78ece2a9ec2d8bf826b36e5.jpg"
]

async def aiodownload(url):
# aiohttp.ClientSession() <==> requests
name = "4.7img/"+url.rsplit("/")[-1]
async with aiohttp.ClientSession() as session: # 发送请求
async with session.get(url, verify_ssl=False) as resp: # 得到图片
# resp.text()/resp.json()
# 保存到文件 扩展:aiofile
with open(name, "wb") as f:
f.write(await resp.content.read()) # 等价于resp.content
print(name, ":over")

async def main():
tasks = []
for url in urls:
tasks.append(asyncio.create_task(aiodownload(url)))
await asyncio.wait(tasks)

if __name__ == "__main__":
asyncio.run(main())

4.8异步爬虫实战_扒光一部小说

1.同步操作:访问getCatalog 拿到所有章节的cid和名称
2.异步操作:访问getChapterContent 下载所有文章和内容
目录url: https://dushu.baidu.com/api/pc/getCatalog?data={“book_id”:“4355370985”}
文章内容url: https://dushu.baidu.com/api/pc/getChapterContent?data={“book_id”:“4355370985”,“cid”:“4355370985|1566855961”,“need_bookinfo”:1}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import requests
import asyncio
import aiohttp
import aiofiles
import json

async def aiodownload(cid, bid, title):
data = {
"book_id": f"{bid}",
"cid": f"{bid}|{cid}",
"need_bookinfo": 1
}
data = json.dumps(data)
url_content = f"https://dushu.baidu.com/api/pc/getChapterContent?data={data}"
async with aiohttp.ClientSession() as session:
async with session.get(url_content, verify_ssl=False) as resp:
dic = await resp.json()
name = "4.8novel/"+title+".txt"
async with aiofiles.open(name, "w", encoding="utf-8") as f:
await f.write(dic['data']['novel']['content'])


async def getCatalog(u):
resp = requests.get(u)
dic = resp.json()
tasks = []
for item in dic['data']['novel']['items']:
title = item['title']
cid = item['cid']
# print(cid, title)
tasks.append(asyncio.create_task(aiodownload(cid, book_id, title)))
# 准备异步任务
await asyncio.wait(tasks)

if __name__ == "__main__":
book_id = "4355370985"
url = 'https://dushu.baidu.com/api/pc/getCatalog?data={"book_id":' + book_id + '}'
asyncio.run(getCatalog(url))

4.9 综合训练_抓取91视频

4.9.1 综合训练_视频网站的工作原理

1
<video src="/Users/lzh/Documents/workSpace/pycharm/crawl/第3章/video.mp4"></video>

一般视频网站是怎么做的?

  • 用户上传 -> 转码(把视频做处理,不同清晰度) -> 切片处理(把单个文件拆分)
  • 用户在拉动进度条的时候,直接从某切片开始加载

需要一个文件记录: m3u+utf8=m3u8m3u + 'utf-8' = m3u8 文本格式

  1. 视频播放顺序
  2. 视频存放路径
  3. 其他信息

想要抓取一个视频:

  1. 找到m3u8(各种手段)
  2. 通过m3u8下载ts文件
  3. 可以通过各种手段(包括但不限于编程),把ts文件合并为mp4

比较常用和关键的字段:(其他详见m3u8关键字段
 • EXTM3U:这个是M3U8文件必须包含的标签,并且必须在文件的第一行,所有的M3U8文件中必须包含这个标签。
 • EXT-X-VERSION:M3U8文件的版本,常见的是3(目前最高版本应该是7)。
 • EXT-X-TARGETDURATION:该标签指定了*==单个媒体文件持续时间的最大值==*,播放文件列表中的媒体文件在EXTINF标签中定义的持续时间必须小于或者等于该标签指定的持续时间。该标签在播放列表文件中必须出现一次。
 • EXT-X-MEDIA-SEQUENCE:M3U8直播是的直播切换序列,当播放打开M3U8时,以这个标签的值作为参考,播放对应的序列号的切片。
 • EXTINF:为M3U8列表中每一个分片的duration,如上面例子输出信息中的第一片的duration为2.969秒。在EXTINF标签中,除了duration值,还可以包含可选的描述信息,主要为标注切片信息,使用逗号分隔开。
 • EXT-X-KEY:表示怎么对media segments进行解码。其作用范围是下次该tag出现前的所有media URI,格式如下:

1
    #EXT-X-KEY:<attribute-list>:

​ NONE 或者 AES-128。如果是NONE,则URI以及IV属性必须不存在,如果是AES-128(Advanced EncryptionStandard),则URI必须存在,IV可以不存在。
​ 对于AES-128的情况,keytag和URI属性共同表示了一个key文件,通过URI可以获得这个key,如果没有 IV(Initialization Vector),则使用序列号作为IV进行编解码,将序列号的高位赋到16个字节的buffer中,左边补0;如果 有IV,则将改值当成16个字节的16进制数。

4.9.2 综合训练_抓取91看剧(简单)

流程:

  1. 拿到html页面源代码
  2. 从源码中提取m3u8的url
  3. 下载m3u8,下载视频
  4. 合并视频

下载m3u8

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import requests
import re
# 用来提取m3u8的url
obj = re.compile(r"url: '(?P<url_m3u8>.*?)',", re.S)
url = "http://91kanju2.com/vod-play/62916-3-1.html"
resp = requests.get(url)
# 提取到m3u8地址
url_m3u8 = obj.search(resp.text).group("url_m3u8")
resp.close()
# 下载m3u8
resp_m3u8 = requests.get(url_m3u8)
with open("重生之门.m3u8", "wb") as f:
f.write(resp_m3u8.content)
resp_m3u8.close()
print("over")

解析m3u8

1
2
3
4
5
6
7
8
9
10
11
n = 1
with open("重生之门.m3u8", mode="r", encoding="utf-8") as f:
for line in f:
line = line.strip() # 去掉空格等
if line.startswith("#"): # 不需要#开头的信息
continue
resp_ts = requests.get(line)
f = open(f"4.9video_91/{n}.ts", mode="wb")
f.write(resp_ts.content)
n += 1
print("完成了第n个ts下载")

4.9.3 综合训练_抓取91看剧(复杂)

思路

  1. 拿到主页面的页面源代码,找到iframe
  2. 从iframe的源代码中拿到m3u8文件
  3. 下载第一层m3u8文件 -> 下载第二层m3u8文件(ts存放路径)
  4. 下载ts文件
  5. 下载密钥,进行解密
  6. 合并ts为一个mp4
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
import requests
import asyncio
import aiohttp
import aiofiles
from Crypto.Cipher import AES
import os
# 获取框架内iframe的url
def get_iframe_src(ur):
# resp_iframe = requests.get(ur)
# main_page = BeautifulSoup(resp_iframe.text, "html.parser")
# src = main_page.find("iframe").get("src")
# print(src)
# return src
return "https://kanju77.com/xgplayer.html?url=https://youku.sd-play.com/20220820/9jja4ALL/index.m3u8"
# 获取第一个m3u8文件url
def get_first_m3u8_url(ur):
# resp_first_m3u8 = requests.get(ur)
# print(resp_first_m3u8.text)
# obj = re.compile(r'var main = "(?P<m3u8_url>.*?)"', re.S)
# m3u8_url = obj.search(resp_first_m3u8.text).group("m3u8_url")
m3u8_url = ur.split("=")[-1]
# print(m3u8_url)
return m3u8_url
# 下载m3u8文件
def download_m3u8_file(ur, name):
resp = requests.get(ur)
with open("4.9.3video_91/" + name, mode="wb") as f:
f.write(resp.content)
# 异步下载ts文件
async def download_ts(ur, fname, sess):
async with sess.get(ur, verify_ssl=False) as resp:
async with aiofiles.open(f"4.9.3video_91/{fname}", mode="wb") as f:
await f.write(await resp.content.read())
print(f"{fname}下载完毕")
# 读取m3u8文件进行异步下载主框架
async def aio_download(name):
tasks = []
async with aiohttp.ClientSession() as session: # 提前准备好session
async with aiofiles.open(name, mode="r", encoding="utf-8") as f:
async for line in f:
if line.startswith("#"):
continue
ts_url = line.strip()
task = asyncio.create_task(download_ts(ts_url, line.split("/")[-1].strip(), session))
tasks.append(task)
await asyncio.wait(tasks)
# 获取key.key文件中的密钥
def get_key(ur):
resp = requests.get(ur)
return resp.text
# 异步解密ts文件
async def decode_ts(fn, k):
aes = AES.new(key=k.encode("utf-8"), IV=b"0000000000000000", mode=AES.MODE_CBC)
async with aiofiles.open(f"4.9.3video_91/{fn}", mode="rb") as f1, \
aiofiles.open(f"4.9.3video_91/temp_{fn}", mode="wb") as f2:
bs = await f1.read() # 从源文件读取
await f2.write(aes.decrypt(bs))
print(f"{fn} over!")
# 读取m3u8文件进行异步解密文件
async def aio_decode(k):
tasks = []
async with aiofiles.open("4.9.3video_91/second.m3u8", mode="r", encoding="utf-8") as f:
async for line in f:
if line.startswith("#"):
continue
fname = line.split("/")[-1].strip()
# 开始创建异步任务
task = asyncio.create_task(decode_ts(fname, k))
tasks.append(task)
await asyncio.wait(tasks)
# 终端命令合并ts文件为mp4
def merge_ts():
# mac: cat 1.ts 2.ts 3.ts > xxx.mp4(正则"*.ts")
# windows: copy /b 1.ts+2.ts+3.ts xxx.mp4
lst = []
with open("4.9.3video_91/second.m3u8", mode="r", encoding="utf-8") as f:
for line in f:
if line.startswith("#"):
continue
fname = line.split("/")[-1].strip()
lst.append(f"4.9.3video_91/temp_{fname}")
s = " ".join(lst)
os.system(f"cat {s} > 4.9.3video_91/movie.mp4")
print("all over!")
# 主函数框架
def main(ur):
# 主页面源代码 -> iframe url
iframe_src = get_iframe_src(ur)
# 拿到第一层的m3u8文件
first_m3u8_url = get_first_m3u8_url(iframe_src)
# 下载第一层m3u8
download_m3u8_file(first_m3u8_url, "first.m3u8")
print("m3u8_1 over")
# 下载第二层m3u8
with open("4.9.3video_91/first.m3u8", mode="r", encoding="utf-8") as f:
for line in f:
if line.startswith("#"):
continue
else:
line = line.strip()
# 拼接第二层m3u8下载路径
second_m3u8_url = first_m3u8_url.split("/20220820/")[0] + line
download_m3u8_file(second_m3u8_url, "second.m3u8")
print("m3u8_2 over")
# 异步协程下载ts启动
asyncio.run(aio_download("4.9.3video_91/second.m3u8"))
# 拿到密钥
key_url = second_m3u8_url.replace("index.m3u8", "key.key")
key = get_key(key_url)
# 异步解密启动
asyncio.run(aio_decode(key))
# 合并ts文件
merge_ts()

if __name__ == "__main__":
url = "https://f.vhttps.com/vy/113082-2-1/"
main(url)

第五章 selenium

5.1 selenium引入

能不能让我的程序连接到浏览器,让浏览器来完成各种复杂的操作,我们只接受最终的成果?

selenium:自动化测试工具 -> 打开浏览器,像人一样去操作浏览器。程序员可以从selenium中直接提取网页上的各种信息

环境搭建:

1
pip install selenium

下载浏览器驱动 把解压缩的浏览器驱动 chromedriver 放在python解释器所在文件夹

让selenium启动谷歌浏览器:

1
2
3
4
5
6
7
8
from selenium.webdriver import Chrome

# 1.创建浏览器对象
web = Chrome()
# 2.打开一个网址
web.get("http://www.baidu.com")
# 3.输出网站名称
print(web.title)

5.2 selenium各种操作_抓拉钩

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from selenium.webdriver import Chrome
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
import time

web = Chrome()
web.get("http://lagou.com")

# 找到某个元素,并点击
el = web.find_element(By.XPATH, '//*[@id="changeCityBox"]/p[1]/a')
el.click() # 点击事件

time.sleep(1) # 让浏览器完全加载完

# 找到输入框,输入python -> 输入回车/点击搜索按钮
web.find_element(By.XPATH, '//*[@id="search_input"]').send_keys("python", Keys.ENTER)

# 查找存放数据的位置,进行数据提取
# 找到数据条
div_lst = web.find_elements(By.XPATH, '//*[@id="jobList"]/div[1]/div')
for div in div_lst:
time.sleep(0.1)
job_name = div.find_element(By.TAG_NAME, "a").text
job_price = div.find_element(By.XPATH, './div[1]/div[1]/div[2]/span').text
job_company = div.find_element(By.XPATH, './div[1]/div[2]/div[1]/a').text
print(job_company, job_name, job_price)

5.3 selenium_窗口之间的切换

1
2
3
4
5
6
7
8
9
10
11
12
13
from selenium.webdriver import Chrome
from selenium.webdriver.common.by import By
import time
# 打开网页
web = Chrome()
web.get("http://lagou.com")
# 关闭窗口
web.find_element(By.XPATH, '//*[@id="cboxClose"]').click()
time.sleep(1)
# 搜索python并点击信息跳转新窗口
web.find_element(By.XPATH, '//*[@id="search_input"]').send_keys('python\n')
time.sleep(0.8)
web.find_element(By.XPATH, '//*[@id="jobList"]/div[1]/div[1]/div[1]/div[1]/div[1]/a').click()

如何进入新窗口提取内容?

1
2
3
4
5
6
7
8
9
10
# 注意,在selenium的眼中,新窗口默认是不切换的,虽然浏览器页面已切换
web.switch_to.window(web.window_handles[-1])
# 新窗口提取
job_detail = web.find_element(By.XPATH, '//*[@id="job_detail"]/dd[2]/div')
print(job_detail.text)
# 关闭新窗口
web.close()
# 变更selenium视角回到原窗口
web.switch_to.window(web.window_handles[0])
print(web.find_element(By.XPATH, '//*[@id="jobList"]/div[1]/div[1]/div[1]/div[1]/div[1]/a').text)

如果页面中遇到了iframe怎么处理?

1
2
3
4
5
6
web.get("https://f.vhttps.com/vy/113082-2-1/")
# 处理iframe需要先拿到iframe,然后切换视角到iframe,再从中拿数据
iframe = web.find_element(By.XPATH, '//*[@id="playleft"]/iframe')
web.switch_to.frame(iframe)
print(web.find_element(By.XPATH, '/html/head/title').text)
# web.switch_to.default_content() # 切换回默认页面

5.4 无头浏览器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import time
from selenium.webdriver.chrome.options import Options
from selenium.webdriver import Chrome
from selenium.webdriver.support.select import Select
from selenium.webdriver.common.by import By
# 准备好参数配置
opt = Options()
opt.add_argument("--headless")
opt.add_argument("--disable-gpu")
web = Chrome(options=opt) # 把参数设置到浏览器:不弹出浏览器窗口
web.get("https://ys.endata.cn/BoxOffice/Ranking")
# 定位到下拉列表
sel_el = web.find_element(By.XPATH, "xxx")
# 对元素进行包装
sel = Select(sel_el)
# 让浏览器进行调整选项
for i in range(len(sel.options)): # i为下拉选框的索引位置
sel.select_by_index(i) # 根据索引切换
time.sleep(1)
table = web.find_element(By.XPATH, 'xxxx')
print(table.text)
web.close()

# 如何拿到页面代码元素(经过数据加载和js执行后的html内容)
print(web.page_source)

5.5 超级鹰处理验证码

1.图像识别

2.选择互联网上成熟的验证码破解工具——超级鹰.py

5.6 超级鹰处理超级鹰

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import time
from selenium.webdriver.chrome.options import Options
from selenium.webdriver import Chrome
from selenium.webdriver.common.by import By
from chaojiying import Chaojiying_Client
opt = Options() # 避免浏览器自动关闭
opt.add_experimental_option("detach", True)
web = Chrome(options=opt)
web.get("http://www.chaojiying.com/user/login/")

# 处理验证码
img = web.find_element(By.XPATH, '/html/body/div[3]/div/div[3]/div[1]/form/div/img').screenshot_as_png
chaojiying = Chaojiying_Client('19997617865', 'thebest1', '938270')
dic = chaojiying.PostPic(img, 1902)
verify_code = dic['pic_str']
# 向页面中填充信息
web.find_element(By.XPATH, '/html/body/div[3]/div/div[3]/div[1]/form/p[1]/input').send_keys("19997617865")
web.find_element(By.XPATH, '/html/body/div[3]/div/div[3]/div[1]/form/p[2]/input').send_keys("thebest1")
web.find_element(By.XPATH, '/html/body/div[3]/div/div[3]/div[1]/form/p[3]/input').send_keys(verify_code)

# 点击登陆
web.find_element(By.XPATH, '/html/body/div[3]/div/div[3]/div[1]/form/p[4]/input').click()
time.sleep(3)

5.7 搞定12306的登陆问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import time
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver import Chrome
from selenium.webdriver.common.action_chains import ActionChains
from chaojiying import Chaojiying_Client
# 初始化超级鹰
chaojiying = Chaojiying_Client("19997617865", "thebest1", "938270")
# # 如果程序被识别,chrome的版本大等于88
# opt = Options()
# opt.add_experimental_option('excludeSwitches', ['enable-automation'])
# opt.add_argument('--disable-blink-features=AutomationControlled')
# web = Chrome(options=opt)
opt = Options() # 避免浏览器自动关闭
opt.add_experimental_option("detach", True)
web = Chrome(options=opt)
web.get("https://kyfw.12306.cn/otn/resources/login.html")
time.sleep(2)
web.find_element(By.XPATH, '//*[@id="toolbar_Div"]/div[2]/div[2]/ul/li[1]/a').click()
time.sleep(2)

# 先处理验证码
verify_img_content = web.find_element(By.XPATH, 'img_xpath')
# 用超级鹰去识别验证码
dic = chaojiying.PostPic(verify_img_content.screenshot_as_png, 9004)
result = dic['pic_str'] # [xi,yi]...
rs_lst = result.split("|")
for rs in rs_lst:
tmp = rs.split(",")
x = int(tmp[0])
y = int(tmp[1])
# 鼠标移动至坐标点并点击
# 事件链
ActionChains(web).move_to_element_with_offset(verify_img_content, x, y).click().perform()
time.sleep(1)
# 输入用户名和密码
web.find_element(By.XPATH, '//*[@id="J-userName"]').send_keys('19997617865')
web.find_element(By.XPATH, '//*[@id="J-password"]').send_keys('thebest1')
# 点击登录
web.find_element(By.XPATH, '//*[@id="J-login"]').click()
time.sleep(3)
# 拖拽
btn = web.find_element(By.XPATH, '//*[@id="nc_1_n1z"]')
ActionChains(web).drag_and_drop_by_offset(btn, 300, 0).perform()

专题:干b站视频

步骤:

  1. 弄明白b站如何加载视频:
  2. 抓包工具抓包/拿源代码
  3. 写正则
  4. 转化为字典,url
  5. 下载视频音频
  6. 合并起来