寻找加密位置 JS hook
寻找hook的点, 比如设置header设置参数,加密解密
hook最常用的场景是加密解密, cookie, 请求参数等
编写hook逻辑
请求调试
应用场景
过debugger
拦截请求,注入ws
主动debugger即寻找参数
函数替换,函数置空、函数覆盖
JSON.parse JSON.parse用于将 JSON 格式的字符串解析为 JavaScript 对象, 它通常用于处理从服务器接收到的 JSON 数据。1 2 3 4 5 6 7 8 (function ( ) { var parse = JSON .parse ; JSON .parse = function (arg ) { console .log ("您猜怎么着?断住了! --> " , arg); debugger ; return parse (arg); } })()
XHR断点 1 2 3 4 5 6 7 8 9 10 (function ( ){ var xhr = window .XMLHttpRequest .prototype .open ; window .XMLHttpRequest .prototype .open = function (method, url, async ) { if (typeof url === 'string' && url.indexOf ("comp" ) !== -1 ) { console .log ("您猜怎么着?断住了! --> " , method, url, async ); debugger ; } return xhr.apply (this , arguments ); }; })();
1 2 3 4 5 6 7 8 9 10 (function ( ){ var sh=window .XMLHttpRequest .prototype .setRequestHeader ; window .XMLHttpRequest .prototype .setRequestHeader = function (key, value ) { if (key=="enc" ){ console .log ("您猜怎么着?断住了! --> " , key, value); debugger ; } return sh.apply (this , arguments ); }; })()
cookie 请求每个页面的cookie都有同一个参数,但是值是加密的而且会发生变化,那么大概率它是反爬参数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 (function ( ){ var ck='' ; Object .defineProperty (document , 'cookie' , { set : function (val ) { if (val.indexOf ("v" ) != -1 ) { console .log ("您猜怎么着?断住了! --> " , val); debugger ; } ck = val; return val; }, get : function ( ) { return ck; } }) })()
置空 1 2 3 4 function deg ( ){ debugger ; } debugger = function ( ){}
xhr 使用方法
从请求网址中选择一小段关键字
Source—>XHR/fetch Breakpoints—>+
输入关键字
断住之后跟栈分析
跟启动器和堆栈 适用于任何有启动器的请求
使用方法
打开chorme开发者工具的network
选择涉及目标数据的文件
点击启动器Initiator
根据加密解密的具体情况分析加密解密的位置,然后在对应的位置添加断点
搜索 document类型的请求时候搜索
使用方法
点击chorme开发者工具右上角的三个点图标
选择’Search’
注意这里使用的是全局的搜索,开发者工具栏左侧的搜索框是局部搜索,只能搜索当前network中的文件
搜索技巧
目标参数名,例如: total
给参数名加上双引号, "total"
给参数名加上单引号, 'total'
给参数名加上冒号, total:
加上点,.total
头部中的参数
勾选Network-Headers-Response Headers-Raw,不然显示的参数可能和原始参数的大小写存在区别,导致我们搜不到或者hook不到
header[
加密库
encrypt
AES
Utf8.parse
解密库
decrypt
js基础 字符串转字典 当接收的数据打印是字典,但实际是字符串,就可以通过json
库对其进行转换
可以使用type()
查看数据类型
1 2 response = requests.get(url).text response = json.loads(response)
格式化输出字典
无限debugger
在加载网页时,浏览器运行script会触发debugger
clearInterval() 1 2 3 y=setInterval (function ( ){ debugger ; },3000 )
遇到这种情况可以使用clearInterval()
,清除这个定时器
这里的5为interval
的编号
一律不在此处暂停
右键点击debugger所在行的左侧,然后点击Never pause here(一律不在此处暂停)
增加条件断点 右键点击debugger所在行的左侧,然后点击Add conditional breakpoint(添加条件断点)
不一定要写false
,只要语句等于false
都可以
停用断点
大家都别玩了
构造器 1 2 3 4 5 6 7 8 9 10 11 (() => { Function .prototype .__constructor = Function ; Function = function ( ) { if (arguments && typeof arguments [0 ] === "string" ) { if ("debugger" === arguments [0 ]) { return ; } } return Function .apply (this , arguments ); } })();
eval 1 2 3 4 5 6 7 8 eval_a = eval eval = function (a ) { if (a === '(function() {var a = new Date(); debugger; return new Date() - a > 100;}())' ) { return null ; } else { return eval_a(a); } }
控制台状态检测 使用debugger卡控制台 原理 1 2 3 4 5 6 7 <script> function check ( ) { debugger ; setTimeout (check, 1000 ); } check (); </script>
解决办法 方法一:右键点击debugger所在行的左侧,然后点击Never pause here(一律不在此处暂停)
方法一:hook掉这个检测函数
1 2 3 4 5 (function ( ){ check=function ( ){ console .log ("前端检测开发者工具是否打开" ); }; })();
格式化检测
检测代码中是否出现\n
,因为在格式化代码时,会在代码中添加许多换行。
检测代码行数,代码格式化之后行数可能会增加
浏览器高度差检测 可以让控制台从页面分离出来
js原理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 const browerOuterHeight = window .outerHeight ;const browerInnerHeight = window .innerHeight ;const browerOuterWidth = window .outerWidth ;const browerInnerWidth = window .innerWidth ;const heightThreshold = 150 ;const widthThreshold = 150 ;if (browerOuterHeight - browerInnerHeight > heightThreshold || browerOuterWidth - browerInnerWidth > widthThreshold) { console .log ('This is a browser with toolbars' ); window .location .href = "https://www.baidu.com" ; }
运行时间检测 1 2 3 4 5 6 7 8 9 10 11 ort = Date .now () function a ( ) { debugger ; current_time = Date .now () if ((current_time - org) > 2000 ) { console .log ('检测超时' ); } else { console .log ('检测正常' ); } }
禁止控制台输出 原理 1 setInterval ('console.clear()' , 1000 )
解决办法 console—>setting—>Preserve log
原理 console.log
被置空
1 console .log = function ( ){}
解决办法 在网页加载时就用一个变量备份住console.log
宇哥过9ku.com的调试思路 9ku.com 这个网站打开控制台后会跳转到其他页面
尝试禁用全部断点—》不管用
搜devtools,搜到了(浏览者开发工具中设置网络为低速3G),但是由于发生页面跳转,结果很快就过去了
找这个网站最后一个请求,截取url里面的部分字符’aaa’,打一个xhr断点: 没断住
再找请求,Payload里有个参数key包含”debugger”,十分可疑,再打个xhr断点’key’: 断住了
有混淆,从断住的地方往下走,边走边搜’devtools’,’console’,没有发现
重新跟栈,找到跳转的地方
本地替换代码
反调试 https://www.aqistudy.cn/
这种一般是检测网页窗口,只需要给调试窗口独立出来就可以了
hook 想通过hook查找的变量需要是全局变量
浏览器手动注入 监听cookie中的‘__dfp’的设置
清空浏览器数据 避免跳过加载我们想要的参数
chrome—>F12—>Application—>Clear site data
再将相关全局变量设置为空(这需要后面跟栈时发现)
或者可以开一个无痕窗口
在网页加载时断住 有以下几种方法
找到第一个js文件的开始,设置一个断点
Event Listener Breakpoints—>Load—>load(第一个文件运行断点,比方法1的断点位置落后一点点)
在控制台运行脚本 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 (function ( ) { 'use strict' ; let cookieTemp = '' ; Object .defineProperty (document , 'cookie' , { set : function (val ) { if (val.indexOf ('__dfp' ) !== -1 ) { debugger ; } console .log ('Hook catch cookie:' , val); cookieTemp = val; return val; }, get : function ( ) { return cookieTemp; } }) })();
跟栈 刷新网页,然后运行到脚本debugger处,发现目标参数被赋值,就可以跟栈了。如果是服务器赋的值,就可以在network中搜索这个值,找到返回这个值的请求
油猴脚本注入
新建脚本,直接给上面的代码复制到这里面
设置脚本运行的网页// @match *://www.iqiyi.com/*
,设置运行脚本的时间setting->General->Run at->document-start
fiddler 编程猫插件(须自行安装)—>注入hook—>填入代码—>删掉地址栏内容—>勾选开启
常用hook代码 JSON.parse JSON.parse()
方法用于将一个JSON字符串转换为对象,在某些站点的加密过程中可能会遇到,以下代码演示了遇到JSON.parse()
时,则插入断点:
1 2 3 4 5 6 7 8 (function ( ) { var parse = JSON .parse ; JSON .parse = function (params ) { console .log ("Hook JSON.parse-->" , params); debugger ; return parse (params); } })();
JSON.stringify JSON.stringify()
方法用于将JavaScript值转换为JSON字符串,在某些站点的加密过程中可能会遇到,以下代码演示了遇到JSON.stringify()
时,则插入断点:
1 2 3 4 5 6 7 8 (function ( ) { var stringify = JSON .stringify ; JSON .stringify = function (params ) { console .log ("Hook JSON.stringify-->" , params); debugger ; return stringify (params); } })();
eval JavaScript eval()
函数的作用是计算JavaScript字符串,并把它作为脚本代码来执行。如果参数是一个表达式,eval()
函数将执行表达式。如果参数是Javascript语句,eval()
将执行javascript语句,经常被用来动态执行JS。以下代码执行后,之后所有的eval()
操作都会在控制台打印输出将要执行的JS源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 (function ( ) { window .__cr_eval = window .eval ; var myeval = function (src ) { console .log (src); console .log ("==================eval end=======================" ); debugger ; return window .__cr_eval (src); } var _myeval = myeval ().bind (null ); _myeval.toString = window .__cr_eval .toString ; Object .defineProperty (window , 'eval' , { value : _myeval }) })();
1 2 3 4 5 6 7 8 (function ( ) { var parse_ = JSON .parse ; JSON .parse = function (jp ) { console .log ("您猜怎么着?断住了! --> " , jp); debugger ; return parse_ (jp); } })();
寻找加密入口 搜索
参数名
关键字
发送加密:
encrypt
接收数据:
decrypt
JSON.parse
success:
部分文件路径名
搜索文件路径名方法 应对document类型的请求,无法使用跟栈
测试
通过搜索标签的属性(例如id),来找到操作此块标签的函数
搜索id
添加断点
代码,启动!!!
一目了然,找到加密函数
跟栈调试 跟栈调试原理 xhr类型的数据包适合使用跟栈方法
在发包的时候断住,然后就可以选择向前或者向后找
同步调试
发送数据加密xhr
在发包处打断点
通过调用栈观察调用关系
接收数据解密
接收数据处打断点:逐语句运行F11
网页渲染出打DOM断点:观察调用栈的关系
混淆 jsfuck jsfuck
解决办法 HOOK eval, 变量a就是原代码
1 2 3 eval = function (a ) { debugger ; }
AAencode和JJ混淆 加密工具网址
解决办法
一般最后一个括号()
内是自运行函数,所以只要将最后一个括号和括号里的内容替换成.toString()
,即可看到目的代码
HOOK Function
1 2 3 Function .prototype .constructor = function (params ) { debugger ; }
webpack 原理 Webpack 是一个模块打包工具,它可以将多个模块(如 JavaScript、CSS、图片等)打包成一个或多个文件,以便在浏览器中高效加载。通过分析模块间的依赖关系,Webpack 能够优化资源加载,提升网页性能。
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 !function (t ) { function e (s ) { if (i[s]) return i[s].exports ; var n = i[s] = { exports : {}, id : s, loaded : 1 }; return t[s].call (n.exports , n, n.exports , e), n.loaded = !0 , n.exports } var i = {}; e ('a' ); }({ a : function ( ) { console .log ('Function 0' ) }, b : function ( ) { console .log ('Function 1' ) } })
解决办法
找到所有webpack文件,复制到一个文件内找到加载器,导出加载器
删除初始化代码,通过加载器调用加解密函数
确保在浏览器中运行成功
确保Node.js中运行成功
精简代码—修改加载器输出调用代码,替换源函数
基础案例 01天气 利用api获取城市天气
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import requestsimport jsonimport pprinturl = "http://api.openweathermap.org/data/2.5/weather?q=wuhan&mode=json&units=metric&lang=zh_cn&APPID=83b30d525bff7db3e396e99abc62bd75" response = requests.get(url).text response = json.loads(response) result = {} result["city" ] = response["name" ] result["temp" ] = response["main" ]["temp" ] result["temp_max" ] = response["main" ]["temp_max" ] result["temp_min" ] = response["main" ]["temp_min" ] result["weather" ] = response["weather" ][0 ]["description" ] for i in result: print (result[i])
爬虫基础 urllib 使用urllib爬取百度的页面 1 2 3 4 5 6 7 8 9 10 11 import urllib.requesturl = "http://www.baidu.com" reponse = urllib.request.urlopen(url=url) content = reponse.read() with open (file='./1.txt' , mode='w' ) as fp: fp.write(content)
当我试图将content保存到txt文件中,发生了报错。
这里我们得到的content是二进制文件,我们需要将其转化为我们能看懂的格式,也就是进行编码一下。
fp.write(content.decode('utf-8'))
百度页面就成功写入到我们的1.txt
文件中了。
response的类型和方法 resposne = urllib.requests.urlopen(url=url)
resposne
的类型:<class 'http.client.HTTPResponse'>
方法
read
默认读取全部,传递数字的话就是指定读取几个字节
readline
readlines
getcode
获取状态码
geturl
获取url
getheaders
获取请求头
urllib下载(urlretrieve) urlretrieve(第一参数是文件的网络地址,第二个是本地地址)
下载百度源码
1 2 3 4 import urllib.requesturl = "http://www.baidu.com" response = urllib.request.urlretrieve(url=url, filename='baidu.html' )
urlretrive
也可用于下载图片和视频等网络资源
1 2 3 4 5 6 7 8 import urllib.requestimg_url = "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fc-ssl.duitang.com%2Fuploads%2Fitem%2F202007%2F01%2F20200701114519_umgxc.png&refer=http%3A%2F%2Fc-ssl.duitang.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1677571326&t=f80dcf2ac99b58b32bb6e834691a2576" video_url = "https://vd4.bdstatic.com/mda-ngw9spwz9353bm7c/360p/h264/1659250469860282338/mda-ngw9spwz9353bm7c.mp4?v_from_s=hkapp-haokan-suzhou&auth_key=1674981205-0-0-9b8ff1b4f8accbfc3835e1fda6de811f&bcevod_channel=searchbox_feed&pd=1&cd=0&pt=3&logid=0205360586&vid=131734394297378935&abtest=&klogid=0205360586" urllib.request.urlretrieve(img_url, 'lisa.jpg' ) urllib.request.urlretrieve(video_url, '搞笑.mp4' )
quote 用于将字符串转化为unicode
编码
使用场景:在我们进行百度的时候要使用unicode编码,而如果我们直接搜索https://www.baidu.com/s?wd=周杰伦
,当然这样在浏览器中是被允许的,可是在爬虫的工作中,却无法访问到页面,我们需要将”周杰伦”转换成unicode编码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import urllib.requestimport urllib.parseurl = "https://www.baidu.com/s?wd=" name = input ('请输入你想搜索的内容:' ) name = urllib.parse.quote(name) url = url + name headers = { 'user-agent' : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36' } request = urllib.request.Request(url=url, headers=headers) response = urllib.request.urlopen(request) content = response.read().decode('utf-8' ) print (content)
urlencode 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import urllib.parseimport urllib.requestdata = {'wd' : '周杰伦' , 'sex' : '男' , 'location' : '台湾' } data = urllib.parse.urlencode(data) headers = { 'user-agent' : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36' } url = "https://www.baidu.com/s?" + data request = urllib.request.Request(url=url, headers=headers) response = urllib.request.urlopen(request) content = response.read().decode('utf-8' ) print (content)
案例:百度翻译 百度翻译和上面的搜索都使用了urlencode,而urlcode将字典转换成了字符串,搜索是将内容放在url里面,而百度翻译则是使用post传data。所以二者所需data的类型也不同,前者不需要编码,而后者需要编码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import urllib.requestimport urllib.parseimport jsonurl = 'https://fanyi.baidu.com/sug' headers = { 'user-agent' : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36' } something = input ('请输入你想翻译的内容:' ) data = {'kw' : something} data = urllib.parse.urlencode(data).encode('utf-8' ) request = urllib.request.Request(url=url, data=data, headers=headers) response = urllib.request.urlopen(request).read().decode('utf-8' ) obj = json.loads(response) print (obj)
案例:豆瓣电影前十页分页存储-url+ajax 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 def create_requests (page ): """create_requests 对传入的页号所对应的页面进行爬取并返回一个该页面的response _extended_summary_ Args: page (int): 页号 Returns: json: 页面的json文件 """ url = 'https://movie.douban.com/j/chart/top_list?type=24&interval_id=100%3A90&action=&' data = {'start' : (page - 1 ) * 20 , 'end' : page * 20 } headers = { 'user-agent' : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36' } import urllib.parse import urllib.request data = urllib.parse.urlencode(data) url += data request = urllib.request.Request(url=url, headers=headers) response = urllib.request.urlopen(request).read().decode('utf-8' ) return response def download (response, page ): """download 将页面的json数据存储到对应文件 _extended_summary_ Args: response (str): 页面数据 page (int): 用于修改对应文件名 """ with open ('豆瓣电影' + str (page) + '.json' , 'w' , encoding='utf-8' ) as fp: fp.write(response) if __name__ == '__main__' : start_page = int (input ('请输入起始页码:' )) end_page = int (input ('请输入终止页码:' )) for page in range (start_page, end_page + 1 ): response = create_requests(page) download(response, page)
案例:kfc餐厅地址-post+ajax 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 def create_requests (page ): """create_requests 对传入的页号所对应的页面进行爬取并返回一个该页面的response _extended_summary_ Args: page (int): 页号 Returns: json: 页面的json文件 """ url = 'http://www.kfc.com.cn/kfccda/ashx/GetStoreList.ashx?op=cname' data = { 'cname' : '北京' , 'pid' : '' , 'pageIndex' : '2' , 'pageSize' : '10' , } headers = { 'user-agent' : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36' } import urllib.parse import urllib.request data = urllib.parse.urlencode(data).encode('utf-8' ) request = urllib.request.Request(url=url, data=data, headers=headers) response = urllib.request.urlopen(request).read().decode('utf-8' ) return response def download (response, page ): """download 将页面的json数据存储到对应文件 _extended_summary_ Args: response (str): 页面数据 page (int): 用于修改对应文件名 """ with open ('kfc_vme50_' + str (page) + '.json' , 'w' , encoding='utf-8' ) as fp: fp.write(response) if __name__ == '__main__' : start_page = int (input ('请输入起始页码:' )) end_page = int (input ('请输入终止页码:' )) for page in range (start_page, end_page + 1 ): response = create_requests(page) download(response, page)
URLError,HTTPError
HTTPError类是URLError类的子类
导入的包urllib.error.HTTPError urllib.error.URLError
http错误:http错误是针对浏览器无法连接到服务器而增加出来的错误提示。引导并告诉浏览者该页是哪里出了问题。
通过urllib发送请求的时候,有可能会发送失败,这个时候如果想让你的代码更加的健壮,可以通过try-except进行捕获异常,异常有两类,URLError\HTTPError
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 import urllib.requestimport urllib.errorheaders = { 'user-agent' : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36' } try : url1 = "https://shabinumberone.com/1123" request = urllib.request.Request(url=url1, headers=headers) reponse = urllib.request.urlopen(request).read().decode('utf-8' ) except urllib.error.HTTPError: print ('起飞失败' ) except urllib.error.URLError: print ('系统正在升级!!!' ) try : url2 = "https://www.bilibili.com/video/BV1eA411C7uiii" request = urllib.request.Request(url=url2, headers=headers) reponse = urllib.request.urlopen(request).read().decode('utf-8' ) except urllib.error.HTTPError: print ('起飞失败' ) except urllib.error.URLError: print ('系统正在升级!!!' )
Handler 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import urllib.requesturl = 'http://www.baidu.com' headers = { 'user-agent' : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36' } request = urllib.request.Request(url=url, headers=headers) handler = urllib.request.HTTPHandler() opener = urllib.request.build_opener(handler) response = opener.open (request) content = response.read().decode('utf-8' ) print (content)
xpath 获取百度一下的文字 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 from lxml import etreeimport urllib.requesturl = 'http://www.baidu.com' headers = { 'user-agent' : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36' } request = urllib.request.Request(url=url, headers=headers) reponse = urllib.request.urlopen(request) content = reponse.read().decode('utf-8' ) tree = etree.HTML(content) result = tree.xpath('//input[@id="su"]/@value' )[0 ] print (result)
批量下载图片(无反爬) 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 import urllib.requestfrom lxml import etreeimport timedef create_request (page ): if (page == 1 ): url = 'https://ssr1.scrape.center' else : url = 'https://ssr1.scrape.center/page/' + str (page) headers = { 'user-agent' : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36' } request = urllib.request.Request(url=url, headers=headers) reponse = urllib.request.urlopen(request) content = reponse.read().decode('utf-8' ) return content def get_img (content ): tree = etree.HTML(content) img_list = tree.xpath('//*[@id="index"]/div[1]/div[1]/div//img/@src' ) name_list = tree.xpath('//*[@id="index"]/div[1]/div[1]/div//h2/text()' ) for i in range (len (name_list)): name = name_list[i] src = img_list[i] url = 'ht' + src[2 :] import os if (os.path.exists('./movie_img' ) == False ): os.mkdir('./movie_img' ) urllib.request.urlretrieve(url=url, filename='./movie_img/' + name + '.jpg' ) if __name__ == '__main__' : start_page = int (input ("请输入第一页:" )) end_page = int (input ("请输入最后一页:" )) for page in range (start_page, end_page + 1 ): print ('正在下载第' + str (page) + '页' ) content = create_request(page) get_img(content) print ('Done!!!' ) time.sleep(1 )
jsonpath jsonpath教程
jsonpath读取网站电影名 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import urllib.requestimport jsonpathimport jsonheaders = { 'user-agent' : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36' , } url = 'https://spa1.scrape.center/api/movie/?limit=10&offset=0' request = urllib.request.Request(url=url, headers=headers) reponse = urllib.request.urlopen(request) obj = json.load(reponse) content = jsonpath.jsonpath(obj, '$..name' ) print (content)
BeautifulSoup 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 from bs4 import BeautifulSoupsoup = BeautifulSoup(open ('./.vscode/19bs4.html' , encoding='utf-8' ), 'lxml' ) obj = soup.select('#p1' )[0 ] print (obj.name) print (obj.attrs) print (obj.attrs.get('class' ))print (obj.get('class' ))print (obj['class' ])
正则表达式 常用元字符 元字符:具有固定含义的特殊符号
.
除换行符之外任意字符
\w
字母或数字或下划线
\s
任意的空白符
\d
数字
\n
一个换行符
\t
一个制表符
^
字符串的开头
$
字符串的结尾
\W
非字符或数字或下划线
\D
非数字
\S
非空白符
a|b
字符a或字符b
()
匹配括号内的表达式,也表示一个组
[...]
匹配字符组中的字符
[^...]
匹配除了字符组中字符的所有字符
量词 量词:控制前面的元字符出现的次数
*
重复零次或更多次
+
重复一次或更多次
?
重复零次或一次
{n}
重复n次
{n,}
重复n次或更多次
{n,m}
重复n次到m次
贪婪匹配和惰性匹配
selenium 安装chromedriver和selenium
下载chromedriver.exe
将chromedriver.exe复制到chrome安装路径(chrome的exe同级目录),同时复制到python的目录里
将chrome的安装路径添加到path
pip install slenium
selenium的元素定位 元素定位:自动化要做的就是模拟鼠标和键盘来操作来操作这些元素,点击、输入等等。操作这些元素前首先要找到它们,WebDriver提供很多定位元素的方法:find_element
简单上手:
1 2 3 4 5 6 7 8 from selenium import webdriverfrom selenium.webdriver.common.by import Bybrower = webdriver.Chrome() url = 'https://www.baidu.com/' brower.get(url) button = brower.find_element(By.XPATH, '//input[@id="su"]' )
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 from selenium import webdriverfrom selenium.webdriver.common.by import Byimport timebrower = webdriver.Chrome() url = 'https://www.baidu.com/' brower.get(url) time.sleep(2 ) input = brower.find_element('id' , "kw" )input .send_keys('周杰伦' )time.sleep(2 ) search = brower.find_element('id' , "su" ) search.click() time.sleep(2 ) js_bottom = 'document.documentElement.scrollTop=100000' brower.execute_script(js_bottom) time.sleep(2 ) next_page = brower.find_element(By.XPATH, "//a[@class='n']" ) next_page.click() time.sleep(2 ) brower.back() time.sleep(2 ) brower.forward() time.sleep(3 ) brower.quit()
chrome handless 这一段配置是写好的,可以直接照抄
1 2 3 4 5 6 7 8 9 10 11 12 13 14 from selenium import webdriverfrom selenium.webdriver.chrome.options import Optionsdef share_browser (): chrome_options = Options() chrome_options .add_argument( ' --headless' ) chrome_options.add_argument( ' --disable-gpu' ) path = r'C:\Program Files\Google\Chrome\Application\chrome.exe' chrome_options. binary_location = path browser = webdriver.Chrome(chrome_options=chrome_options) return browser browser = share_browser() url = 'https://www.baidu.com' browser.get(url)
requests response的属性以及类型
类型
models.Response r.tex
r.text
获取网站源码
r.encoding
访问或定制编码方式
r.url
获取请求的ur
v
响应的字节类型
r.status_code
响应的状态码
r.headers
响应的头信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import requestsurl = 'https://fanyi.baidu.com/sug' headers = { 'User-Agent' : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36' } data = {'kw' : 'eye' } response = requests.post(headers=headers, url=url, data=data) content = response.text import jsonobj = json.loads(content) print (obj)
post请求不需要编解码
post请求的参数是data
requests登陆古诗文网
首先通过输出错误的账号密码,找到登陆接口(在network里面,名字一般带login)
分析request的data
1 2 3 4 5 6 7 8 9 10 data = { '__VIEWSTATE' : '5RPZ6LJU7KKM7C7VzCFf2pL9LmIteosVELlLLdamdHcTFpGAd6na5T0tne6tzfX6q1THIJeEE0+aUQKJg4vp8UUVXyQqyov9iuZj6ue9cwUv3hkD5alVFu7Gq+QjnGurP+b5enWBUerjStO1NnS+5BlDgjw=' , '__VIEWSTATEGENERATOR' : 'C93BE1AE' , 'from:http' : '//so.gushiwen.cn/user/collect.aspx' , 'email' : '2982534677@qq.com' , 'pwd' : 'EXWRa6NrQq572Ufdsa' , 'code' : 'U46S' , 'denglu' : '登录' , }
可以发现VIEWSTATE, VIEWSTATEGENERATOR,code的是不固定的值,在页面源码中寻找
存储方式 1.文本文件 像是txt文件或者MP3,MP4,json等文件可以使用python的file操作函数
以requests为例子,写入字符文件时需要进行编码处理,而对字节文件则不用。 但是要注意读取response的类型的不同,一个是text ,一个是content
1 2 3 4 5 6 7 8 response = requests.get('' ) content = response.text with open ('a.txt' , 'w' , encoding = 'utf-8' )as fp: fp.write(context) with open ('a.json' ,'wb' )as fp: fp.write(response.content)
使用pandas存储excel 1 2 3 4 5 data = pd.DataFrame() data['name' ] = img_name data['score' ] = img_score data.to_excel("movie.xlsx" , index=False )
将数据存储到mysql 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 mysql_local = { 'host' : '127.0.0.1' , 'port' : 3306 , 'user' : 'root' , 'password' : '' , 'db' : 'data' } data = zip (img_name, img_grade) conn = pymysql.connect(**mysql_local) cursor = conn.cursor() sql = f'INSERT INTO film_info(film_name,score)VALUES("%s",%s)' cursor.executemany(sql, data) conn.commit()
scrapy
流程
创建爬虫的项目 scrapy startproject projectname
,注意:projectname不能数字开头和携带中文
创建爬虫文件
在projectname/spiders
文件夹内去创建爬虫文件
创建爬虫文件scrapy genspider 爬虫文件的名字 要爬取的网页
eg:scrapy genspider baidu www.baidu.com
运行爬虫代码 scrapy crawl
爬虫的名字
注释掉ROBOTSTXT_OBEY = True
,那么我们的爬虫就不遵守robots协议了
scrapy项目结构 项目名字
spiders文件夹(存储的是爬虫文件)
init
自定义的爬虫文件———scrapy项目的核心功能文件
init
items(定义数据结构,爬取的数据都包含哪些)
middleware(中间件-代理)
pipelines(管道-用来处理下载的数据)
settings(配置文件-robots协议-ua定义等)
response的属性和方法
response.text 获取的是响应的字符串
response.body 获取的是二进制数据
response.xpath 可以直接通过xpath方法来解析response中的内容
response.extract() 提取seletor对象的data属性值
response.extract_first() 提取的seletor列表的第一个数据
scrapy工作流程
引擎向spiders要url
引擎将要爬取的url给调度器
调度器会将url生成请求对象放入到指定的队列中
从队列中出列一个请求
引擎将请求交给下载器进行处理
下载器发送请求获取互联网数据
下载器将数据返回给引擎
引擎将数据再次给到spiders
spiders通过xpath解析该数据,得到数据或者url
spiders将数据或者url给到引擎
引擎判断该数据是url还是数据,交给管道(item pipeline)处理,是url交给调度器处理
Feed Exports scrapy 提供的Feed Exports可以将抓取的所有内容以json,jsonlines,csv, xml, pickle, marshal等格式输出
1 2 3 4 5 6 7 8 scrapy crawl quotes -o quotes.json scrapy crawl quotes -o quotes.js scrapy crawl quotes -o quotes.jsonlines scrapy crawl quotes -o quotes.csv scrapy crawl quotes -o quotes.xml scrapy crawl quotes -o quotes.pickle scrapy crawl quotes -o quotes.marshal scrapy crawl quotes -o ftp://user:pass@ftp.example.com/path/to/quotes.csv
scrapy案例:quotes quotes是scrapy的官方示例案例。网址
每个单位有三个数据:text,author,tags
1、新建项目 scrapy startproject scrapytutorial
新建项目
scrapy genspider quotes quotes.toscrape.com
创建爬虫文件
注释ROBOTSTEXT
,不遵守robots协议
2、quotes.py 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import scrapyfrom scrapytutorial.items import ScrapytutorialItemclass QuotesSpider (scrapy.Spider): name = "quotes" allowed_domains = ["quotes.toscrape.com" ] start_urls = ["https://quotes.toscrape.com" ] def parse (self, response ): print ("=" * 100 ) quotes = response.xpath("//div[@class='quote']" ) for quote in quotes: text = quote.xpath('span[@class="text"]/text()' ).extract_first().replace('"' , '' ) author = quote.xpath('span/small[@class="author"]/text()' ).extract_first() tags = quote.xpath('div[@class="tags"]/a/text()' ).extract() item = ScrapytutorialItem(text=text, author=author, tags=tags) yield item next = response.xpath('//li[@class="next"]/a/@href' ).extract_first() url = response.urljoin(next ) yield scrapy.Request(url=url, callback=self.parse)
3、items.py 1 2 3 4 5 6 7 8 9 10 11 12 13 14 import scrapyclass ScrapytutorialItem (scrapy.Item): text = scrapy.Field() author = scrapy.Field() tags = scrapy.Field()
4、保存数据 保存数据到一个json文件中
1 scrapy crawl quotes -o quotes.json
每一个item输出到一行json文件
1 2 3 scrapy crawl quotes -o quotes.jsonlines # 下面是上面的简写 scrapy crawl quotes -o quotes.jl
上面都是保存到一个文件内,第一种是一个[]在外面,里面的每个数据用{}包住,{}之间有’,’分割。下面的方法没有[]和’,’。
5、保存到mongodb settings.py 1 2 3 4 5 6 7 8 9 ITEM_PIPELINES = { 'scrapytutorial.pipelines.TextPipeline' : 300 , 'scrapytutorial.pipelines.MongoDBPipeline' : 400 , } MONGODB_CONNECTION_STRING = 'localhost' MONGODB_DATABASE = 'scrapytutorial'
CrawlSpider
继承自scrapy.Spider
独门秘笈 CrawlSpider可以定义规则,再解析html内容的时候,可以根据链接规则提取出指定的链接,然后再向这些链接发送请求 所以,如果有需要跟进链接的需求,意思就是爬取了网页之后,需要提取链接再次爬取,使用crawlSpider是非常合适的
提取链接 链接提取器,在这里就可以写规则提取指定链接
1 2 3 4 5 6 7 scrapy.linkextractors.LinkExtractor( allow=(), deny=(), allow_domains=(), deny_domains=(), restrict_xpaths=(), restrict_css=()
模拟使用 正则用法:links1 = LinkExtractor(allow=r'list_23_\d+\.html')
xpath用法:links2 = LinkExtractor(restrict_xpaths=r'//div[@class="x"]')
css用法:links3 = LinkExtractor(restrict_css='.x')
提取连接link.extract_links(response)
CrawlSpider案例 需求:读书网数据入库
创建项目:crapy startproject dushuproject
跳转到spiders路径 cd\dushuproject\dushuproject\spiders
创建爬虫类:scrapy genspider -t crawl read www.dushu.com
items
spiders
settings
pipelines 数据保存到本地 数据保存到mysq1数据库
数据入库
settings配置参数:
1 2 3 4 5 6 DB_HOST = '192.168.231.128' DB_PORT = 3306 DB_USER = 'root' DB_PASSWORD = '1234' DB_NAME = 'test' DB_CHARSET = 'utf8'
管道配置
1 2 3 from scrapy.utils.project import get_project_settings import pymysqlclass MysqlPipeline (object ):#_init_方法和open _spider的作用是一样的