Python爬虫理论
铺垫内容
爬虫分类
通用爬虫:
- 抓取系统重要组成部分
聚焦爬虫:
- 建立在通用爬虫的基础之上
- 抓取的为抓取页面局部内容
增量式爬虫:
- 检测网站中数据的更新情况
反爬机制
- 门户网站,可以通过指定相应的策略,防止爬虫程序进行数据的窃取
- 反反爬策略:破解反爬策略,获取数据
相关协议
-
- 君子协议。规定了网站中哪些数据可以被爬取,哪些不可以被爬取
-
- 常用客户端与服务器的通信协议
-
- user-Agent:请求载体的身份标识
- connection:请求完毕后是断开连接还是保持连接
-
- content-type:服务器相应客户端的数据类型
-
- 安全的超文本传输协议
-
对称密钥加密: 密文和密钥均由客户机发送给服务器 缺陷:密钥和密文可能会被中间机构拦截
非对称密钥加密: 密文由客户机发送给服务器 密钥由服务器发送给客户机 缺陷:不能保证客户机拿到的密钥一定由服务器提供
证书密钥加密(https): 由第三方认证机制进行密钥防伪认证
requests模块
requests作用
模拟浏览器发送请求
-
- text:文本格式
- json:json对象
- content:图片格式
UA伪装(反爬机制)
门户网站若检测到请求载体为request而不是浏览器,则会使得拒绝访问
聚焦爬虫
数据解析分类
- 正则
- bs4
- xpath
bs4
-
1. 实例化beautysoup对象,并将源码数据加载到beautysoup中 2. 通过调用beautysoup对象中相关属性和方法进行标签定位和数据提取
-
- soup.tagName:找到第一次出现的标签的属性
- soup.find(): 1. find(tagName):等同于soup.tagName 2. find(tagName,class / attr / id ...):按照属性进行定位
- soup.find_all():查找符合要求的所有标签(列表新式),也可以作为属性定位
- soup.select(): 1. 标签选择器 2. 层级选择器: - 父标签 > 子标签(一个层即) - ‘ ’空格表示多个层即
- Attention:对于find和select的结果非同一对象
-
- soup.text
- soup.string
- soup.get_text()
-
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
46import requests
import json
from bs4 import BeautifulSoup
if __name__ == "__main__":
url = "https://www.shicimingju.com/book/sanguoyanyi.html"
headers = {
"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.61 Safari/537.36"
}
response = requests.get(url=url,headers=headers)
response.encoding = response.apparent_encoding #设置编码格式
"""
其中 r.encoding 根据响应头中的 charset 判断网站编码,如果没有设置则默认返回 iso-8859-1 编码,而r.apparent_encoding
则通过网页内容来判断其编码。令r.encoding=r.apparent_encoding就不会出现乱码问题。
"""
html = response.text
# print(html)
soup = BeautifulSoup(html,'lxml')
muluList = soup.select(".book-mulu a")
muluRecord = []
for mulu in muluList:
muluRecord.append(mulu.text)
pageNum = len(muluRecord)
dataTotalUrl = "https://www.shicimingju.com/book/sanguoyanyi/%d.html"
for i,title in enumerate(muluRecord):
dataUrl = dataTotalUrl%(i + 1)
response = requests.get(url=dataUrl,headers=headers)
response.encoding = response.apparent_encoding
dataHtml = response.text
dataSoup = BeautifulSoup(dataHtml,'lxml')
data = dataSoup.find("div",class_="chapter_content").text
data = data.replace(" ","\n")
path = r"C:\Users\Y_ch\Desktop\spider_test\data\text\sanguo\\" + title + ".txt"
with open(path,'w',encoding="utf-8") as fp:
fp.write(data)
print("第%d篇下载完毕"%(i + 1)
xpath
-
- 实例化etree对象,且需要将页面源码数据加载到被解析对象中去
- 调用etree中的方法,配合着etree中的xpath方法定位
-
- 将本地的html源码数据加载到etree中
- etree.parse(filepath)
- 可以将互联网上获得的源码数据加载到etree中去
- etree.HTML(text)
- 将本地的html源码数据加载到etree中
-
- 绝对路径:/xx/xx/x
- 省略路径://xx
- 属性定位://tagName[@attr = ""]
- 索引定位://tagName[@attr=""]/xx
- 重复索引://tagName[@attr]//p[pos]
- 文本获取://tagName/text()
- 属性获取://tagName/@attr
-
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
44import json
from lxml import etree
import requests
if __name__ == "__main__":
url = "https://pic.netbian.com/4kdongman/index_%d.html"
headers = {
"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.61 Safari/537.36"
}
pageNum = 2
for page in range(pageNum):
if page == 0:
new_url = "https://pic.netbian.com/4kdongman/"
else:
new_url = url % (page + 1)
response = requests.get(url=new_url, headers=headers)
html_code = response.text
tree = etree.HTML(html_code)
urlList = tree.xpath("//ul[@class=\"clearfix\"]//img//@src")
urlHead = "https://pic.netbian.com"
path = r"C:\Users\Y_ch\Desktop\spider_test\data\pic\4K\\"
picUrlList = []
for index, eachUrl in enumerate(urlList):
picUrl = urlHead + eachUrl
picUrlList.append(picUrl)
for index, picUrl in enumerate(picUrlList):
picReq = requests.get(url=picUrl, headers=headers)
pic = picReq.content
picPath = path + str(page)+ "." +str(index) + ".jpg"
with open(picPath, 'wb') as fp:
fp.write(pic)
print("第%d页 第%d张图片下载成功!" % ((page + 1),index + 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
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
85import json
import requests
from lxml import etree
from verication import vercation
if __name__ == "__main__":
url = "https://so.gushiwen.cn/user/login.aspx?from=http://so.gushiwen.cn/user/collect.aspx"
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.61 Safari/537.36"
}
response = requests.get(url=url,headers=headers)
tree = etree.HTML(response.text)
varication_path = tree.xpath("//img[@id=\"imgCode\"]/@src")
picUrl = "https://so.gushiwen.cn" + varication_path[0]
pic = requests.get(url=picUrl,headers=headers).content
print(vercation(pic=pic))
#!/usr/bin/env python
# coding:utf-8
import requests
from hashlib import md5
class Chaojiying_Client(object):
def __init__(self, username, password, soft_id):
self.username = username
password = password.encode('utf8')
self.password = md5(password).hexdigest()
self.soft_id = soft_id
self.base_params = {
'user': self.username,
'pass2': self.password,
'softid': self.soft_id,
}
self.headers = {
'Connection': 'Keep-Alive',
'User-Agent': 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)',
}
def PostPic(self, im, codetype):
"""
im: 图片字节
codetype: 题目类型 参考 http://www.chaojiying.com/price.html
"""
params = {
'codetype': codetype,
}
params.update(self.base_params)
files = {'userfile': ('ccc.jpg', im)}
r = requests.post('http://upload.chaojiying.net/Upload/Processing.php', data=params, files=files, headers=self.headers)
return r.json()
def ReportError(self, im_id):
"""
im_id:报错题目的图片ID
"""
params = {
'id': im_id,
}
params.update(self.base_params)
r = requests.post('http://upload.chaojiying.net/Upload/ReportError.php', data=params, headers=self.headers)
return r.json()
def vercation(pic,picCode=1902,picMoudle=None):
chaojiying = Chaojiying_Client('1325650083', 'ych3362632', '94271a5f53dc7b0e34efdb06a88692c1')
if picMoudle == None:
return chaojiying.PostPic(pic, picCode)["pic_str"]
else :
im = open(pic, 'rb').read() # 本地图片文件路径 来替换 a.jpg 有时WIN系统须要//
return chaojiying.PostPic(im, picCode)["pic_str"]
# if __name__ == '__main__':
# chaojiying = Chaojiying_Client('1325650083', 'ych3362632', '94271a5f53dc7b0e34efdb06a88692c1') #用户中心>>软件ID 生成一个替换 96001
# im = open('a.jpg', 'rb').read() #本地图片文件路径 来替换 a.jpg 有时WIN系统须要//
# print (chaojiying.PostPic(im, 1902)) #1902 验证码类型 官方网站>>价格体系 3.4+版 print 后要加()
代理
-
- 代理服务器
-
- 突破自身IP的限制
- 隐藏自身真实的IP
-
- 快代理
- 西祠代理
- www.goubanjia.com
-
- 透明:服务器知到代理ip和真实ip
- 匿名:服务器知到代理ip,但不知道真实ip
- 高匿:服务器即不知道代理ip,也不知道真实ip
-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16from lxml import etree
import requests
if __name__ == "__main__":
url = "https://www.baidu.com/s?wd=ip"
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.61 Safari/537.36"
}
proxies = {
"https":"221.110.147.50:3128"
}
response = requests.get(url=url,headers=headers,proxies=proxies)
with open(r"C:\Users\Y_ch\Desktop\spider_test\dd.html",'w') as fp:
fp.write(response.text)
异步爬虫
异步爬虫的方式
-
- 好处:可以为相关阻塞的操作开启线程或进程,阻塞的操作就可以异步执行
- 弊端:无法无限制的开启线程和进程
-
- 好处:降低系统对线程或者进程的创建和销毁的效率,从而更好的降低对系统的开销
- 弊端:线程池的线程个数有上线
selenium模块
-
- http://chromedriver.storage.googleapis.com/index.html
-
- 便捷的获取网站中动态加载的数据(使用etree和soup不能解析的js文件也可以获取)
- 便捷的实现模拟登录
-
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
62from selenium import webdriver
from lxml import etree
import requests
import time
from multiprocessing.dummy import Pool
"""
使用线程池爬取,容易被反爬虫措施进行拦截!!!
"""
headers = {
"Useer-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.61 Safari/537.36"
}
def getUrlList():
url = "https://www.pearvideo.com/category_5"
response = requests.get(url=url, headers=headers)
htmlHead = 'https://www.pearvideo.com/'
initHtml = etree.HTML(response.text)
videoUrlList = initHtml.xpath("//ul[@class=\"category-list clearfix\"]//a/@href")
print((videoUrlList))
videoHtml = []
for each in videoUrlList:
videoHtml.append(htmlHead + each)
return videoHtml
def get_video(url):
if url == None:
return
bro.get(url=url)
page_text = bro.page_source
tree = etree.HTML(page_text)
try:
videoUrl = tree.xpath("//div[@class=\"main-video-box\"]/div//@src")[0]
name = tree.xpath("//h1[@class=\"video-tt\"]/text()")[0]
video = requests.get(url=videoUrl, headers=headers).content
path = r"C:\Users\Y_ch\Desktop\spider_test\data\video\pear\\" + name + ".mp4"
with open(path, 'wb') as fp:
fp.write(video)
print(name + " 视频下载成功!")
except IndexError as e:
print(url)
bro = webdriver.Chrome('./chromedriver.exe')
url = getUrlList()
get_video(url[1])
pool = Pool(len(url))
print(len(url))
pool.map(get_video,url)
pool.close()
pool.join()
time.sleep(10)
bro.close() -
- 通过get方法进行url的请求
-
- 通过find的系列函数得到指定标签元素
-
- 通过send_keys("xxx")进行标签的交互
-
- 通过执行excute_script("
")来是页面执行js代码
- 通过执行excute_script("
-
- back()
- forward()
-
- close()
-
- save_screenshoot("./filepath")
iframe处理
-
- ```python bro.switch_to.frame("ifrmaeResult") #切换frame框
bro.find_element_by_id("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
### 动作链
- [ ] 当需要在浏览器中进行动作处理时,使用webdriver的动作链进行处理
- [ ] 代码样例:
```python
def drop_test():
bro = webdriver.Chrome("chromedriver.exe")
bro.get("https://www.runoob.com/try/try.php?filename=jqueryui-api-droppable")
bro.switch_to.frame("iframeResult")
div = bro.find_element_by_id("draggable")
#构造动作链
action = ActionChains(bro) #构造动作链实例
action.click_and_hold(div) #点击并长按
for i in range(5):
#move_by_offset():
#xoffset,yoffset:两个方向移动的像素
#perform():
#立即执行
action.move_by_offset(xoffset=18,yoffset=0).perform()
time.sleep(0.3)
#释放动作链
action.release()
time.sleep(5)
bro.close()
print(div)
- ```python bro.switch_to.frame("ifrmaeResult") #切换frame框
bro.find_element_by_id("1")
无头浏览器
-
1
2
3
4
5
6
7from selenium.webdriver.chrome.options import Options
chrome_option = Options()
chrome_option.add_argument("--headless")
chrome_option.add_argument("--disable-gpu")
bro = webdriver.Chrome("./chromedriver.exe",chrome_options=chrome_option) #在driver的实例化中添加chrome_options的属性
selenium屏蔽规避
-
1
2
3
4
5
6# chrome79以前的版本
def evade():
option = ChromeOptions()
option.add_experimental_option("excludeSwitches",["enable-automation"])
bro = webdriver.Chrome("./chromedriver.exe",options=option)1
2
3
4
5
6
7
8
9
10#chrome79以后的版本
from selenuim import webdriver
driver = webdriver.Chrome()
driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
"source": """
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined
})
"""
})
Scrapy框架
初始scrapy
-
- 集成了很多功能并且具有很强通用性的一个项目模板
-
- 爬虫的封装好的框架
- 功能:
- 高性能的持久化存储
- 异步的数据下载
- 高性能的数据解析
- 分布式
-
- pip install wheel
在https://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted中找到python对应版本的twisted版本,放在指定目录中,并执行
pip install <Twisted-20.3.0-cp36-cp36m-win_amd64.whl>
- pip install pywin32
- pip install scrapy
-
进入包含scrapy的python环境
执行scrapy startprojecr XXXPro
创建成功
-
scrapy genspider
<初始url> -
scrapy crawl
-
1
2
3
4
5
6
7
8#爬虫文件的名称,是爬虫源文件的唯一表示符
name = 'test'
#爬虫文件允许请求的url,如果请求的url不在该列表内,则不允许进行请求(在通常情况下不使用该参数)
allowed_domains = ['www.xxx.com'] #在一般情况下需要将该参数列表进行注释
#爬虫文件的起始url,即爬虫自动进行访问的url
start_urls = ['http://www.xxx.com/'] -
1
ROBOTSTXT_OBEY = False #需要将其修改为false,否则被网站拒绝
-
scrapy crawl
--nolog 缺陷:如果response错误,无任何提示信息
针对 --log 的缺陷,改进:
在setting文件中设置:LOG_LEVEL = "ERROR"
-
1
2
3def parse(self, response):
div_list = response.xpath("//div[@class=\"content\"]/span/text()").extract()
print(''.join(div_list))
scrapy数据的持久化存储
-
基于终端的存储:
scrapy crawl
-o 注意:
1. 只可以将parse函数的**返回值**进行存储到**本地文件(不可以直接存储到数据库中)**中 2. 只能存储为指定的文件类型:【'json', 'jsonlines', 'jl', 'csv', 'xml', 'marshal', 'pickle'】
基于管道的存储:
编码流程:
- 数据解析
- 在item类中定义相关的属性用于数据的封装
- 将解析的数据封装到item中
- 将item类型的对象提交到管道进行持久化存储
- 在管道类的process_item进行数据的保存
- 在配置文件中开启管道
本地保存实例
1 |
|
数据库保存实例
1 |
|
1 |
|
存储总结
-
- 命令行形式(需要parse返回,且存储类型固定,并且不能存在数据库中)
- 管道形式:除了配置麻烦外全是优点
-
- 建立两个pipe类文件,并将创建的类在配置文件中进行设置
- 若多个管道类文件都进行同步存储,需要高优先级的process_item对item进行返回,使得低优先级的管道类可以获得item数据
全站数据请求
-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17class BeautiSpider(scrapy.Spider):
name = 'beauti'
# allowed_domains = ['www.xxx.com']
start_urls = ['https://www.duodia.com/daxuexiaohua/']
url = "https://www.duodia.com/daxuexiaohua/list_%d.html"
pageNum = 1
def parse(self, response):
div_list = response.xpath("//*[@id=\"main\"]/div[2]/article")
for div in div_list:
name = div.xpath(".//a/@title").extract()
print("".join(name))
if self.pageNum <= 5:
new_url = self.url % self.pageNum
self.pageNum += 1
yield scrapy.Request(url=new_url,callback=self.parse) #递归调用,callback专门用于数据解析
五大核心组件
-
- 用于处理整个系统的数据流处理,触发事物(核心框架)
-
- 用来接收引擎发过来的请求,压入队列中,并在引擎再次请求的时候返回,可以想象成一个URL(抓取网页的网址或者说时链接)的优先队列,由他来决定下一个要抓取的网址是什么,同时去除重复的网址
-
- 用于下载网页的内容,并将网页内容返回给spider(Scrapy下载器时建立在twisted整个高效的异步模型上的)
-
- 负责处理爬虫从网页抽取的实体,主要高能是持久化实体,验证实体的有效性,清楚不需要的信息,当页面被爬虫解析后,被发送到项目管道,并经过几个特定的次序处理数据
-
- 爬虫是主要干活的,用于从特定的网页中提取自己需要的信息,即所谓的实体(item)。用户也可以从中提取链接,让Scrapy继续抓取下一个页面
请求传参
-
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# allowed_domains = ['www.xxx.com']
start_urls = ['https://www.zhipin.com/job_detail/?query=python']
home_url = "https://www.zhipin.com/"
def detail_parse(self,response):
item = response.meta["item"]
content = response.xpath("//*[@id=\"main\"]/div[3]/div/div[2]/div[2]/div[1]/div//text()").extract()
item["content"] = content
yield item
def parse(self, response):
print(response)
li_list = response.xpath("//*[@id=\"main\"]/div/div[3]//li")
for li in li_list:
name_div = li.xpath("//span[@class=\"job-title\"]")
title = name_div.xpath("/span[@class=\"job-name\"]/a/@title").extract()
name = name_div.xpath("/span[@class=\"job-area-wrapper\"]/span/text()").extract()
li_name = title + " " + name
detail = li.xpath("//div[@class=\"primary-wrapper\"]/div/@href").extract()
new_url = "https://www.zhipin.com/" + detail
item = BoproItem()
item["name"] = li_name
yield scrapy.Request(url=new_url,callback=self.etail_parse,meta={"item":item}) #item传入到其他函数中使用
图片管道类(ImagesPipeline)的使用
-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15#pipelines.py
from scrapy.pipelines.images import ImagesPipeline
import scrapy
class ImageLine(ImagesPipeline):
#根据图片地址进行请求
def get_media_requests(self, item, info):
yield scrapy.Request(item["src"][0]) #记住时scrapy.request!!!!!!!!!!
#指定图片存储路径
def file_path(self, request, response=None, info=None, *, item=None):
return item["name"][0] + ".jpg"
def item_completed(self, results, item, info):
return item #返回给下一个即将执行的item1
2
3
4
5#settings.py
ITEM_PIPELINES = {
'imagePro.pipelines.ImageLine': 300,
}
IMAGES_STORE = "./data/pic/beauty"
中间件的使用(middlewares):
-
UA伪装:process_request
1
2
3def process_request(self, request, spider): #进行UA伪装
request.headers["User-Agent"] = xxx
return None代理IP:process_exception
1
2def process_exception(self, request, exception, spider): #进行IP更换
request.meta["proxy"] = xxx
-
篡改相应数据,响应对象:process_response
1
2
3
4
5
6
7
8
9
10
11
12
13
14def process_response(self, request, response, spider):
#挑选出指定对象进行修改
#通过url进行request
#通过resquest对response进行指定
if request.url in spider.href_list: #获得动态加载的页面
bro = spider.bro
bro.get(request.url)
page_text = bro.page_source
new_response = HtmlResponse(url=request.url,body=page_text,encoding="utf-8",request=request)
return new_response
else:
return response
CrawlSpider全站数据爬取
-
创建Scrapy工程
cd XXX
创建爬虫文件(ScrawlSpider):
- scrapy genspider -t crawl
- 链接提取器:根据指定规则(allow = ",<正则表达式>")进行指定链接的提取
- 规则解析器
- scrapy genspider -t crawl
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Yue C.H. Site!