爬虫教程
爬虫教程
Nuyoah需要准备的东西:
python基础, html, css
web请求过程分析
- 服务器的渲染:在服务器那边直接把数据和html整合在一起,统一返回给浏览器
- 在页面源代码中可以看到数据
- 客户端渲染:
- 第一次请求只要一个html骨架,第二次请求拿到数据,进行数据展示
- 在页面源代码中看不到数据
HTTP协议(超文本传输协议)
Http协议吧一条信息分为三大块内容,无论是请求还是相应都是三大块
请求:
1. 请求行 -> 请求方式 请求url地址 协议
2. 请求头 -> 放一些服务器要使用的附加信息
3. 请求体 -> 一般放一些请求参数
相应
1. 状态行 -> 协议 状态码
2. 响应头 -> 放一些客户端要使用的一些附加信息
3. 响应体 -> 服务器返回的真正客户端要使用的内容(Html, json)等
请求头中最常见的一些重要内容(爬虫需要):
- User-Agent:请求载体的身份标识(用啥发送的请求)
- Referer:防盗链(这次请求是从哪个页面来的,反爬会用到)
- cookie:本地字符串数据信息(用户登录信息,反爬的token)
响应头中一些重要的内容
- cookie:本地字符串数据信息(用户登录信息,反爬的token)
- 各种神奇的莫名存在的字符串(这个需要经验,一般都是token,放着各种攻击和反爬)
请求方式:
GET:显示提交
POST:隐式提交
数据解析概述
正则表达式
Regular Expression,正则表达式,一种使用表达式的方式对字符串进行匹配的语法规则
我们抓取到的网页源代码本质上就是一个超长的字符串,想从里面提取内容,则正则再适合不过了
正则优点:速度快,效率高,准确性高
正则的缺点:新手上手难度有点高
不过只要掌握了正则编写的逻辑关系,写出一个提取网页内容的正则其实并不复杂
正则语法:使用元字符进行排列组合用来匹配字符串,可以在正则表达式在线测试
元字符:具有固定含义的特殊符号
常用元字符
1 | 1. . 匹配除换行符以外的任意字符 |
一般一个元字符表示一个字符
一般 ^和配合使用:^\d\d\d
量词:控制前面的元字符出现的次数
1 | * 重复零次或更多次 |
贪婪匹配和惰性匹配
1 | .* 贪婪匹配 |
re表达式
-
findall:匹配字符串中所有的复合正则表达式的内容
- lst = re.findall(r"\d+","我的电话号码是:10086, 我女朋友的电话号码是:10010“)
- print(list) ====== [‘10086’, ‘10010’] // 使用findall返回的是一个列表
- lst = re.findall(r"\d+","我的电话号码是:10086, 我女朋友的电话号码是:10010“)
-
finditer:匹配字符串中所有的内容[返回一个迭代器],从迭代器中获取每个元素使用.group()
-
it = re.finditer(e"\d+", "我的电话号码是:10086, 我女朋友的电话号码是:10010“)
1
2for i in it:
print(i.group)
-
-
search,找到一个结果就返回,返回的结果是match对象,从中拿数据需要使用.group
- s = re.search(r"\d+","我的电话号码是:10086, 我女朋友的电话号码是:10010“)
- print(s.group) ======= 10086// 只返回一个结果
-
match 是从头开始匹配
- s = re.match(r"\d+",“我的电话号码是:10086, 我女朋友的电话号码是:10010”)
- print(s.group) === 会报错 ,因为s为空
- s = re.match(r"\d+","10086, 我女朋友的电话号码是:10010“)
- print(s.group) ==== 10086
-
预加载正则表达式(将正则表达式放在变量中)
1
2
3
4
5obj = re.compile(r"\d+")
ret = obj.finditer("我的电话号码是:10086, 我女朋友的电话号码是:10010")
for it in ret:
print(it.group) -
通过正则表达式获取其中一部分东西
- 获取方法 :(?P<分组名字>正则表达式) 这样可以单独的从正则匹配的内容中进一步提取内容
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20s = """
<div class='jay'><span id='1'>郭麒麟</span></div>
<div class='jj'><span id='2'>宋铁</span></div>
<div class='jolin'><span id='3'>大聪明</span></div>
<div class='sylar'><span id='4'>范思哲</span></div>
<div class='tory'><span id='5'>喜羊羊</span></div>
"""
# 预加载正则表达式
obj = re.compile(r"<div class='.*?'><span id='\d'>(?P<name>.*?)</span></div>")
result = obj.finditer(s)
for it in result:
print(it.group("name"))
// 输出结果
郭麒麟
宋铁
大聪明
范思哲
喜羊羊 -
当我们在使用requests请求的时候如果报错的话,出现
exceptions.SSLError:HTTPSConnectionPool 这种情况,一般是requests函数内部检查出错,我们可以使用:
requests.get(url, verify=Flase) // verify = Flase 去掉安全验证
bs4解析
-
把页面源代码交给BeautifulSoup进行处理,生成bs对象
page = BeautifulSoup(resp.text,“html.parser”)
-
从bs对象中查找数据
1
2
3
4
5
6
7find(标签,属性=值)
find_all(标签,属性=值)
# 当我们使用属性 = 值的时候属性的名称可能和python中的关键字重名,这样会报错
# 现在给出一下解决方法
table = page.find("table", class_="hq_table") # 这里class属性名就和python中的关键字重复了,我们可以加下划线
# 另一种解决方法
table = page.fine("table", attrs={"class" : "hq_table"}) # 这种写法和上面的相同 -
".text"方法可以获取标签中包裹着的文本
-
“.get(‘herf’)” 获取标签的属性
xpath解析
xpath 是在XML文档中搜索内容的一门语言
html是XML的一个子集
Xpath寻找方式是从父节点,到子节点这样的方式寻找
1 | <book> |
1 | <!DOCTYPE html> |
1 | # result = tree.xpath('/html') |
注意:如果我们想要通过Xpath得到一个特定标签中的所有内容的话可以使用一下方法,例如我们先要得到一个div标签在网页中的html形式:
1 | # 我们先通过xpath得到我们先要的div标签 |
requests 进阶概述
模拟浏览器登录 —> 处理cookie
1 | # 登录 --> 得到cookie |
防盗链处理 —>抓取梨视频数据
防盗链的用途:就是在你访问这个页面之前,你必须访问referer指向的网址
页面源代码和F12中出现的代码可能是不同的,F12中出现的可能是动态加载的
代理 ---->防止别封ip
异步操作
多线程, 多进程
进程是资源单位,每一个进程至少有一个线程,线程是执行单位,在平时的时候,我们尽量使用多进程,少使用线程,线程比较消耗内存
多线程导入方法:
1 | def fun(): |
多进程导入方法:
1 | from muliprocessing import Process |
线程池,进程池
线程池:一次性开辟一些线程,我们用户直接给线程池交任务,线程任务由线程池调用来完成
线程池调用方法
1 | import requests |
多进程方法和多项成方法一样
协程
1 | def fun(): |
使用python程序编写协程
- 例子(没有IO流的时候,协程和没有协程时间相同)
1 | import asyncio |
-
存在协程的时候,协程的最长时间是根据函数中停顿的最长时间来确定的
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
43import asyncio
import time
async def fun1():
print("I am fun1")
time.sleep(2)
print("I am fun1")
async def fun2():
print("I am fun2")
time.sleep(3)
print("I am fun2")
async def fun3():
print("I am fun3")
time.sleep(4)
print("I am fun3")
if __name__ == '__main__':
f1 = fun1()
f2 = fun2()
f3 = fun3()
fun = [
f1, f2, f3
]
t1 = time.time()
asyncio.run(asyncio.wait(fun))
t2 = time.time()
print(t2-t1)
'''
I am fun3
I am fun3
I am fun1
I am fun1
I am fun2
I am fun2
9.023541688919067
'''由上面代码可知,即便我们使用了async 修饰需要进行协程的函数,以及使用asyncio。run() 配合 ascynio.wait() 来进行多程序的运行,但是结果依旧是 9秒多,说明此时协程并没有起上作用
主要原因在于:time.sleep(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
37
38
39
40
41
42
43import asyncio
import time
async def fun1():
print("I am fun1")
# time.sleep(2) # 当程序出现同步操作的时候,异步就会终止
await asyncio.sleep(2) # 异步操作的代码
print("I am fun1")
async def fun2():
print("I am fun2")
await asyncio.sleep(3)
print("I am fun2")
async def fun3():
print("I am fun3")
await asyncio.sleep(4)
print("I am fun3")
if __name__ == '__main__':
f1 = fun1()
f2 = fun2()
f3 = fun3()
fun = [
f1, f2, f3
]
t1 = time.time()
asyncio.run(asyncio.wait(fun))
t2 = time.time()
print(t2 - t1)
'''
I am fun3
I am fun1
I am fun2
I am fun1
I am fun2
I am fun3
4.016987562179565
'''由上面的程序所得,该程序运行时间是改程序中持续最长时间的程序所决定
协程程序运行的几种方法:
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# 第一种
if __name__ == '__main__':
f1 = fun1()
f2 = fun2()
f3 = fun3()
fun = [
f1, f2, f3
]
t1 = time.time()
asyncio.run(asyncio.wait(fun))
t2 = time.time()
print(t2 - t1)
# 第二种
async def main():
# 2.1
f1 = fun1()
await f1 # 一般await 挂起操作放在协程对象前面
f2 = fun2()
await f2
f3 = fun3()
await f3
# 2.2 推荐
task = [
asyncio.create_task(fun1()), # 将写协程对象包装成task对象
asyncio.create_task(fun2()),
asyncio.create_task(fun3()),
]
await asyncio.wait(tasks)
if __name__ == '__main__':
t1 = time.time()
asyncio.run(main())
t2 = time.time()
print(t2-t1)为什么推荐使用2.2,因为2.2可以完美的套用在爬虫上面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21async def download(url):
print("Start Download")
await asyncio.sleep(2) # 网络请求 这里可不能使用request.get()操作,因为这是同步代码,不能够再异步操作中使用
print("End Download")
async def main():
url = [
"http://www.baidu.com",
"http://www.bilibili.com",
"http://www.163.com"
]
tasks = []
for url in urls:
d = asyncio.create_task(download(url)) # 这时候download函数并不会进行运作,而是返回一个写成对象
tasks.append(d)
await asyncio.wait(tasks)
if __name__ == '__main__':
asyncio.run(main())异步操作,在requests上面的使用:
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
30import aiohttp
import asyncio
url = [
"http://browser9.qhimg.com/bdr/__85/t010448c46c1ecf7cab.jpg",
"http://browser9.qhimg.com/bdr/__85/t016ad88ddaf2ae2d92.jpg",
"http://browser9.qhimg.com/bdr/__85/t01028e5f2ec69e423d.jpg"
]
async def aindownload(url):
# 发送图片
# 得到图片内容
# 保存到文件
name = url.resplit("/", 1)[1]
async with aiohttp.ClientSession() as session: # requests
async with session.get(url) as resp: # resp.get()
# 请求回来之后,写入文件
# 可以自己去学习一个模块,aiofiles
with open(name, mode = "wb") as f:
f.write(await resp.content.read()) # 读取内容是异步的,需要await挂起
print("name", "搞定")
async def main():
tasks = []
for url in urls:
task.append(asyncio.create_task(download(url)))
await asyncio.wait(tasks)
if __name__ == '__main__':
asyncio.run(main())
selenium(可见即可爬)
可以打开浏览器,像人一样去操作浏览器
然后我们可以直接提取网页中的各种信息
环境搭建:
1 | pip install selenium |
1 | from selenium.webdriver import Chrome |
selenium 处理动态网页的时候有奇效!!!
-
查找元素:
web.find_element_by_xpath(“xpath的路径”)
如果查找的是一个按钮的话可以使用click() 做点击动作
-
输入文字:
1
2
3
4
5
6
7
8
9
10
11from selenium.webdriver import Chrome
from selenium.webdriver.common.keys import Keys
# 创建浏览器
web = Chrome()
# 搜索页面
web.get("https://www.baidu.com")
# 获取该页面的输入框
# 这时候可能报错,因为如果页面还灭有加载出来的话就去寻找这个输入框的话,就会报错
web.find_element_by_xpath("输入框的xpath").send_keys("python",Key.ENTER)
# 这里面的send_keys是向输入框中输入文字, 其中Key.ENTER 是输入回车(特殊含义的字符) -
获取文字:
使用xpath定位到文字对应的标签上面,使用.text 获取文字即可、
-
窗口之间的切换
我们在使用xpath点击网址的时候会出现一个新网页
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24web.find_element_by_xpath("a标签的xpath地址")
# 如何进入到窗口中进行提取:
# 注意,虽然我们打开了一个新的网页,但是默认的话还是在原来的窗口,新窗口默认是不会切换回来的
# 切换方法:
web.switch_to.window(web.window_handles[-1]) # 切换到窗口列表中的最后一个
#新窗口中提取内容
content = web.find_element_by_xpath("需要获取文字的下path路径").text
# 关掉子窗口:
# 注意,即使关掉子窗口之后,selenium的视角也不会切换回来,需要手动切换回来
web.close()
# 切换到原来的窗口
web.switch_to_window(web.window_handles[0])
# 如果页面中遇到了iframe:selenium 是看不见iframe的,我们必须进入到iframe中, 然后才能够拿到数据,!!!iframe里面是一个html 文件的
iframe = web.find_element_by_xpath("iframe 的xpath路径") # 获取到需要切换的哪一个iframe,, 因为一个页面中可能有多个iframe,所以我们要定位到我们需要切换到的哪一个iframe
# 切换到目的iframe
web.switch_to.frame(iframe)
# 切换回原页面:
web.switch_to.default_content() -
下拉列表:
1
2
3
4
5
6
7
8
9
10
11
12
13from selenium.webdriver import Chrome
from selenium.webdriver.support.select import Select
# 先定位到对应的下拉列表:
sel_el = web.find_element_by_xpath("对应的下拉列表的select标签")
# 对元素进行包装:
sel = Select(sel_el)
# 对下拉列表中的元素进行切换
for i in range(len(sel.options)): # i 就是每一个下拉框选项的索引位置
sel.select_by_index(i) # 按照索引进行切换,也可以按照value 和 text
time.sleep(2) # 每一次切换都需要发送一个请求,所以我们在这等两秒钟
table = web.find_element_by_xpath("文本所在的xpath路径")
# 这样就获取到了对应的下拉列表中的数据
-
无头浏览器:
定义:就是我们在使用selenium爬取数据的时候,我们其实不关注浏览器的页面,我们主要关注的是,我们想要的内容,让浏览器在后台运行就行,获取到数据返回给我
1
2
3
4
5
6
7
8
9
10
11from selenium.webdriver import Chrome
from selenium.webdriver.chrome.options import Options
# 准备好参数配置:
opt = Options()
opt.add_argument("--headless")
opt.add_argument("--disbale-gup")
# 把参数配置弄到浏览器中,这样就不会显示浏览器界面了,
web = Chrome(options = opt) -
获取页面代码:(经过数据加载以及js执行之后的html中的内容)
1
web.page_source
破解验证码 ---- 超级鹰
-
直接找到该网站,注册一下,然后获取一下id即可调用
-
我们在获取验证码的时候首先要获取验证码这个图片,然后在进行识别,
1
2#图片的识别:
img = web.find_element_by_xpath("验证码所在的img的xpath路径").screenshot_as_png -
当我们遇到需要点击验证的验证码的时候,我们可以先通过超级鹰,解读验证码,获取到对应位置的横纵坐标 然后移动鼠标到该位置进行点击
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15from selenium.webdriver.common.action_chains import ActionChains
verify_img_element = web.find_element_by_xpath("图片路径")
# 通过超级鹰网站下载的破解验证码的模块破解验证码,返回值是一个字典
dic = chaojiying.PostPic(verify_img_element.screenshot_as_png,9004)
rs_list = result.split("|")
for re in re_list:
p_temp = re.split(",")
x = int(p_temp[0])
y = int(p_time[1])
# 要让鼠标移动到某一个位置,然后进行点击
# 这时候我们需要导入事件链模块:
# from selenium.webdriver.common.action_chains import ActionChains
ActionChains(web).move_to_element_with_offset(verify_img_element,x,y).click().perform()
#上面代码的意思是先移动鼠标到该验证码图片的左上角,然后以左上角为基点,进行偏移,一定要加perform()否则点击指令不会执行
-
如果我们遇到能够识别我们是自动化工具在操纵浏览器的时候
1
2
3
4
5
6
7
8
9
10
11# Chrome版本号小于88 我们可以在web = Chrome()后面加上:
web = Chrome()
web.execute_cdp_cmd("Page,addScriptToRvaluateOnNewDocument", {
"source":"""
window.navigator.webdriver = undefined
Object.defineProperty(navigator,'webdriver',{
get:() =>undefined
})
"""
})
web.get(xxxxx)1
2
3
4
5
6# Chrome版本号大于88 我们可以通过导入option来进行:
from selenium.webdriver.chrome.options import Options
options = Options()
option.add_argument('--disable-blink-features=AutomationControlled')
web = Chrome(options = options)
web.get(xxxx)
5.拖拽按钮:
1 | btn = web.find_element_by_xpath("按钮的xpath路径") |