一.爬虫简介
1.1 什么是爬虫
通过编写程序,模拟浏览器上网,然后让其去互联网上抓取数据的过程。
1.2 爬虫的价值
目前是大数据时代,谁掌握的数据越多,谁就更加具有主导权,获取更多的数据,使这些数据能够数据产品化、商业化。
爬虫工程师(
1.本科及以上学历,计算机相关专业,3年及以上工作经验; 2.熟悉Python/C#/Java语言中的一种,至少3年相关的开发经验 3.有Python分布式抓取系统的开发、架构经验,至少熟悉并使用过一种主流爬虫架构,如Scrapy、Gocolly、Webmagic等优先; 4.熟悉前端页面技术,如html、js、ajax等; 5.有海量数据爬取、解析、清洗、存储相关项目经验; 6.熟悉网页抓取原理及技术,能够总结分析不同网站,网页的结构特点及规律; 7.熟悉反爬策略的应对,能够解决封账号、封IP、验证码、JS加密等问题; 8.熟悉Linux系统环境; 9.熟悉Mysql、Redis、MongoDB等数据库,有过数据库调优和海量数据存储经验者优先。
)
1.3爬虫合法性
法律中不被禁止
具有违法风险
善意爬虫 恶意爬虫
1.4爬虫带来的风险可以体现在如下两方面
爬虫干扰了被访问网站的正常运营
爬虫抓取了受到法律保护的特定类型的数据或信息
1.5如何在使用编写爬虫的过程中避免进入局子的厄运
时常的优化自己的程序,避免干扰被访问网站的正常运行
在使用,传播爬取到的数据时,审查抓取到的内容,如果发现了涉及到用户隐私或商业机密等敏感内容需要及时停止爬取或传播
总结:
1.不要爬敏感信息(比如公民的个人信息)
2.不要占用别人网站的大部分流量
3.不要爬取商业化信息
1.6爬虫在使用场景中的分类
通用爬虫:
抓取系统重要组成部分。抓取的是一整张页面数据。
聚焦爬虫:
是建立在通用爬虫的基础之上。抓取的是页面中特定的局部内容。
增量式爬虫:
监测网站中数据更新的情况。只会抓取网站中最新更新出来的数据。
1.7爬虫的矛与盾
1.8反爬机制
门户网站,可以通过指定相应的策略或者技术手段,防止爬虫程序进行网站数据的爬取。
1.9反反爬策略
爬虫程序可以通过制定相关的策略或者技术手段,破解门户网站中具备的反爬机制,从而可以获取门户网站中相关的数据。
1.10robots.txt协议
君子协议。规定了网站中哪些数据可以被爬虫爬取那些数据不可以被爬取。
1.11 http协议
概念:就是服务器和客户端进行数据交互的一种形式。
1.12常用请求头信息
User-Agent:请求载体的身份标识(谁请求)
Connection:请求完毕后,是断开连接还是保持连接
1.13常用响应头信息
Content-Type:服务器响应回客户端的数据类型
1.14https协议:
安全的http协议,安全的超文本传输协议
1.15加密方式
对称密钥加密
(客户端制定加密方式,然后客户端加密,将加密的信息以及加密方式传递给服务器,服务器解密并且读取密文)
安全隐患(可以拦截传输过程 获取加密信息和密钥)
非对称密钥加密
(服务器端制定加密方式,发送给客户端,客户端将使用该加密方式的密文传输给客户端)
包含公开密钥和私有密钥
安全隐患(1.如果公钥被挟持,第三方可能篡改公钥加密方式,并且将纂改之后的密钥传递给客户端,无法保证公钥是服务器端提供的。
2.非对称加密方式的效率较低
)
证书密钥加密(https采用)
公钥被证书认证机构认证,如果客户端接受的密钥无数字签名(或者数字签名 不对)则该密钥不是服务器传输的签名。
二.requests模块
2.1模块介绍
-urllib模块(古老的网路请求模块,封装爬虫代码十分麻烦)
-request模块(简洁、高效)
2.2request模块介绍
requests模块:python中原生的一款基于网络请求的模块,功能非常强大,简单便捷,效率极高。
作用:模拟浏览器发请求。
2.3requests模块如何使用 (requests模块的编码流程)
一、指定url
二、发起请求
三、获取响应数据
四、持久化存储
2.4 环境安装
pip install requests
2.5实战编码(爬取搜狗首页的页面数据)
# -- coding:utf-8 -- import requests if __name__=="__main__": # step1:指定url url='https://www.sogou.com/' # step2:发起请求 # get方法会返回一个响应对象 response = requests.get(url=url) # step3:获取响应数据.text返回的是字符串形式的响应数据 page_text = response.text print(page_text) # step4:持久化存储 with open('./sogou.html','w',encoding='utf-8') as fp: fp.write(page_text) print('爬取数据结束!')
2.6实战巩固
2.6.1 爬取搜狗指定词条对应的搜索结果页面(简易网页采集器)
采用的反爬机制 (UA检测机制、UA伪装)
#反爬机制 # UA伪装 # UA:User-Agent(请求载体的身份标识) # UA检测:门户网站的服务器会检测对应请求的载体身份标识,如果检测到请求的载体身份标识为某一款浏览器, # 说明该请求是一个正常请求。但是检测到载体身份标识不是基于某一款浏览器的,则标识该请求为不正常请求(爬虫) # 则服务器端就很有可能拒绝该次请求。 #为了防止该情况,每次都要进行UA伪装 # UA伪装:让爬虫对应的请求载体身份标识伪装成某一款浏览器 import requests if __name__ == "__main__": #UA伪装:将对应的User-Agent headers = { 'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.134 Safari/537.36 Edg/103.0.1264.71' } #指定url url='https://www.sogou.com/web?' #处理url携带的参数:封装到字典中 kw=input('enter a word:') param={ 'query':kw } # 发起请求,对指定的url发起的请求对应的是携带参数的,并且请求过程中处理了参数 response = requests.get(url=url,params=param,headers=headers) #获取响应数据 page_text =response.text fileName =kw+'.html' with open(fileName,'w',encoding='utf-8') as fp: fp.write(page_text) print(fileName,"保存成功!")
2.6.2 破解百度翻译
抓取ajax信息,在network中改成XHR,如图,sug,
post请求(携带了参数)
响应数据是一组json数据
import requests import json if __name__ == "__main__": #指定url post_url = "https://fanyi.baidu.com/sug" #post请求参数处理(同get请求一致) word=input("end a word:") data={ 'kw':word } #进行UA伪装 headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.134 Safari/537.36 Edg/103.0.1264.71' } #进行请求发送 response = requests.post(url=post_url,data=data,headers=headers) #获取响应数据;json()方法返回的是obj(如果确认响应的数据是json类型的,才可以使用json()) #观察content-Type dic_obj=response.json() #持久化存储 fileName=word+'.json' fp=open(fileName,'w',encoding='utf-8') json.dump(dic_obj,fp=fp,ensure_ascii=False)#中文不能Ascii编码 print("over")
2.6.3 爬取豆瓣电影分类排行榜
import requests import json if __name__ == '__main__': url='https://movie.douban.com/j/chart/top_list' param={ 'type':'24', 'interval_id':"100:90", 'action':'', 'start':'1',#从接口中的第几部电影去取 'limit':'20',#一次取出的个数是 } headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.134 Safari/537.36 Edg/103.0.1264.71' } response = requests.get(url=url,params=param,headers=headers) list_data=response.json() fp=open('./douban.json','w',encoding='utf-8') json.dump(list_data,fp=fp,ensure_ascii=False) print('over')
2.6.4 爬取肯德基餐厅查询指定地点信息
import requests import json if __name__ =="__main__": post_url='http://www.kfc.com.cn/kfccda/ashx/GetStoreList.ashx?op=keyword' headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.134 Safari/537.36 Edg/103.0.1264.71' } keyword=input('餐厅关键字:') op = { 'cname':'', 'pid':'', 'keyword':keyword, 'pageIndex':"1", 'pagesize':'10', } response=requests.post(url=post_url,data=op,headers=headers) list_json=response.json() fp=open("./kfc.json",'w',encoding='utf-8') json.dump(list_json,fp=fp,ensure_ascii=False) print('over')
2.6.5 爬取药监总局中基于中华人民共和国化妆品生产许可证相关数据(抓取动态加载信息)
首页地址:化妆品生产许可信息管理系统服务平台 (nmpa.gov.cn)
url动态加密暂时解决不了
三.数据解析
3.1 数据解析概述
3.1.1聚焦爬虫:爬取页面中指定的页面内容。(75%以上的需求)
编码流程:
1.指定url
2.发起请求
3.获取响应数据
4.数据解析
5.持久化存储
3.1.2 数据解析分类:
-
正则
-
bs4
-
xpath(重点)
3.1.3 数据解析原理概述
解析的局部的文本内容都会在标签之间或者标签对应的属性中进行存储
1.先进性通用爬虫解析
2.进行指定标签的定位
3.标签或者标签对应的属性中存储的数据值进行提取(解析)
3.2 正则表达式
Regular Expression 正则表达式,一种使用表达式的方式对字符串进行匹配的语法规则。我们抓取到的网页源代码本质上就是一个超长的字符串,想从里面提取内容,用正则合适。
正则的优点:速度快、效率高、准确性高。
正则的缺点:新手上手难度有点高。
正则的语法:使用元字符进行排列组合用来匹配字符串
元字符:具有固定含义的特殊字符
字符 | 作用 |
---|---|
. | 匹配除换行符以外的任意字符(默认只匹配一位字符串) |
\w | 匹配字母或数字或下划线 |
\s | 匹配任意的空白符 |
\d | 匹配数字 |
\n | 匹配换行符 |
\t | 匹配一个制表符 |
^ | 匹配字符串的开始(与下一个元字符匹配) |
$ | 匹配字符串的结尾 |
\W | 匹配非字母或数字或下划线 |
\D | 匹配非数字 |
\S | 匹配非空白符 |
a|b | 匹配字符a或字符b |
() | 匹配括号内的表达式,也表示一个组 |
[...] | 匹配字符组中的字符 |
[^...] | 匹配除了字符组中字符的所有字符 |
量词:控制前面的元字符出现的次数
* | 重复零次或更多次 |
---|---|
+ | 重复一次或更多次 |
? | 重复零次或一次 |
{n} | 重复n次 |
{n,} | 重复n次或更多次 |
{n,m} | 重复n到m次 |
贪婪匹配和惰性匹配
.* | 贪婪匹配 |
---|---|
.*? | 惰性匹配 |
3.3 re模块使用
import re # findall:匹配字符串中所有的符合正则的内容 list=re.findall(r"\d+","我的电话号是:10086,女朋友的电话是:10010") print(list) #finditer:匹配字符串中所有的内容[返回的是迭代器],从迭代器中拿到内容需要.group() it = re.finditer(r"\d+","我的电话号是:10086,女朋友的电话是:10010") for i in it: print(i) print(i.group()) #search,找到一个结果就返回,返回的结果是match对象,拿数据需要.group() s=re.search(r"\d+","我的电话号是:10086,女朋友的电话是:10010") print(s.group()) #match是从头开始匹配 s=re.match(r"\d+","10086,女朋友的电话是:10010") print(s.group()) #预加载正则表达式 obj=re.compile(r"\d+") ret=obj.finditer("我的电话号是:10086,女朋友的电话是:10010") for i in ret: print(i) print(i.group()) ret=obj.findall("我爱你1万年") print(ret) love=''' <div class="a"><span id="1">李逍遥</span></div> <div class="b"><span id="2">赵灵儿</span></div> <div class="c"><span id="3">林月如</span></div> <div class="d"><span id="4">刘晋元</span></div> <div class="e"><span id="5">阿奴</span></div> ''' #re.S:让.能匹配换行符 #(?P<分组名字>正则) 可以单独从正则匹配的内容中进一步提取内容 obj=re.compile(r'<div class=".*?"><span id="(?P<number>\d+)">(?P<name>.*?)</span></div>',re.S) ret=obj.finditer(love) for i in ret: print(i.group("name")) print(i.group("number"))
3.3.1 手刃豆瓣top250电影排行
网址:豆瓣电影 Top 250
#拿到页面源代码 requests #通过re来提取想要的有效信息 re import requests import re import csv url = 'https://movie.douban.com/top250' # 进行UA伪装 headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.134 Safari/537.36 Edg/103.0.1264.71' } response=requests.get(url=url,headers=headers) # print(response.text) page_context = response.text #解析数据 obj = re.compile(r'<li>.*?<div class="item">.*?<span class="title">(?P<name>.*?)</span>.*?<p class="">' r'.*?<br>(?P<year>.*?) .*?<span class="rating_num" property="v:average">(?P<score>.*?)</span>' r'.*?<span>(?P<num>.*?)</span>',re.S) #开始匹配 result=obj.finditer(page_context) fp=open("data.csv",'w') csvwriter=csv.writer(fp) for it in result: dic=it.groupdict() dic['year']=dic['year'].strip() # print(it.group('name')) # print(it.group('score')) # print(it.group('num')) # print(it.group('year').strip()) csvwriter.writerow(dic.values()) fp.close() print('over')
3.3.2 屠戮盗版天堂信息
#1.定位到2022必看片 #2.从2020必看片中提取到子页面的连接地址 #3.请求子页面的连接地址,拿到我们想要的下载地址.... import requests import re import time import random domain ="https://www.dy2018.com/" user_agent_list = [ # "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36", # "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36", # "Mozilla/5.0 (Windows NT 10.0; WOW64) Gecko/20100101 Firefox/61.0", # "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36", # "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.62 Safari/537.36", # "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36", # "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0)", # "Mozilla/5.0 (Macintosh; U; PPC Mac OS X 10.5; en-US; rv:1.9.2.15) Gecko/20110303 Firefox/3.6.15", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.134 Safari/537.36 Edg/103.0.1264.77" ] headers = {'User-Agent': random.choice(user_agent_list)} response=requests.get(url=domain,headers=headers) #去掉安全验证 response.encoding='gb2312' #指定字符集 response.close() #拿到ul里面的li obj1=re.compile(r"2022必看热片.*?<ul>(?P<ul>.*?)</ul>",re.S) obj2=re.compile(r"<a href='(?P<href>.*?)'") obj3=re.compile(r'◎片 名 (?P<movie_name>.*?)<br />.*?' r'<td style="WORD-WRAP: break-word" bgcolor="#fdfddf"><a href="(?P<download>.*?)">',re.S) result=obj1.finditer(response.text) child_href_list=[] for it in result: ul=it.group('ul') #提取子页面链接: result1=obj2.finditer(ul) for it2 in result1: #拼接子页面的url地址:域名+子页面地址 child_href = domain +it2.group('href').strip("/") # print(it2.group('href')) child_href_list.append(child_href) #把子页面链接保存起来 #提取子页面内容 for href in child_href_list: print(href) time.sleep(3) child_response=requests.get(url=href,headers=headers,verify=False) child_response.encoding='gbk' url=obj3.search(child_response.text) print(url.group("movie_name")) print(url.group("download")) # break # print(child_response.text)
3.4 bs4 解析
3.4.1 bs4解析 -HTML语法
3.4.2 bs4解析入门--搞搞菜价(未使用bs4,原网址内容改变)
#安装bs4 import requests import json if __name__ =="__main__": posr_url='http://www.xinfadi.com.cn/getCat.html' headers={ 'User-Agent':"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.134 Safari/537.36 Edg/103.0.1264.77" } data={ 'prodCatid':'1186' } response=requests.post(url=posr_url,data=data,headers=headers) list_json=response.json() fp=open('./food.json','w',encoding='utf-8') json.dump(list_json,fp=fp,ensure_ascii=False) print("nice!!!")
3.4.3 抓取优美图库图片
url=【唯美壁纸】桌面壁纸唯美小清新唯美手机壁纸电脑桌面壁纸高清唯美大全 - 优美图库 (umei.cc)
# 1.拿到主页面的源代码,然后提取到子页面的链接地址,href # 2.通过href拿到子页面的内容,从子页面中找到图片的下载地址 img ->src # 3.下载图片 import requests import time from bs4 import BeautifulSoup if __name__== '__main__': domain='https://www.umei.cc' post_url='https://www.umei.cc/bizhitupian/weimeibizhi/' headers={ 'user-agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.134 Safari/537.36 Edg/103.0.1264.77' } # data={ # 'next':'1', # 'table':'news', # 'action':'getmorenews', # 'limit':'10', # 'small_length':'120', # 'classid':'67' # } response=requests.get(url=post_url,headers=headers); response.encoding='utf-8' #处理乱码 # print(response.text) #把源代码交给bs main_page =BeautifulSoup(response.text,'html.parser') # print(main_page) alist=main_page.find("div",class_='swiper-wrapper after').find_all('a') #把范围第一次缩小 # print(alist) for a in alist: href=domain+a.get('href') #直接通过get就可以拿到属性值 #拿到子页面的源代码 child_page_response=requests.get(url=href,headers=headers) child_page_response.encoding='utf-8' child_page_text=child_page_response.text #从子页面中拿到图片的下载路径 child_page=BeautifulSoup(child_page_text,'html.parser') child_alist=child_page.find("div",class_="content-box").find('img') src=child_alist.get('src') #下载图片 img_response=requests.get(url=src,headers=headers) img_name=src.split("/")[-1] #拿到url中的最后一个/以后的内容 with open("img/"+img_name,mode='wb') as fp: fp.write(img_response.content) #图片内容写入文件 print('nice!!!',img_name) time.sleep(1) print("all over")
3.5 Xpath
3.5.1 xpath解析基础
xpath解析原理: 1.实例化一个etree的对象,且需要将被解析的页面源码数据加载到该对象中 2.调用etree对象中的xpath方法结合xpath表达式实现标签的定位和内容的捕获 环境的安装: pip install lxml 如何实例化一个etree对象 1.将本地的html文档中的源码数据加载到etree对象中: etree.parse(filePath) 2.可以将从互联网上获取的源码数据加载到该对象中 etree.HTML('page_text') xpath('xpath表达式') xpath表达式: /:表示的是从根节点开始定位。表示的是一个层级。 //:表示的是多个层级。可以表示从任意位置开始定位。 属性定位://div[@class="a"] 索引定位://div[@class="a"]/p[2] 索引是从1开始的 取文本: /text() 获取的是标签中直系的文本内容 //text() 标签中非直系的文本内容() 取属性: /@attrName /src
a,html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <h2>热门课程排行榜</h2> <ol> <li>Python程序设计</li> <li>零基础学习html</li> <li>JavaScript全攻略</li> </ol> <h2>最新课程排行</h2> <ol> <li>版本管理工具介绍</li> <li>Canvas 绘图详解</li> <li>Pandas数据分析</li> </ol> <div style="margin-bottom: 50px;margin-top:25px;"> <div style="color: blue; font-size: 14px;"> Example Div </div> <p class="my-class",id="my-p-element"> Example P </p> </div> <div class="a"> <p>1</p> <p>2</p> <p>3</p> <span id="1">李逍遥</span> <img src="../dist/img/huge1.7bebe14c.jpg" alt=""> </div>> <div class="b"><ul> <li><a href="#">胡歌</a></li> <li><a href="#">林依晨</a></li> </ul></div> <div class="c"><span id="3">林月如</span></div> <div class="d"><span id="4">刘晋元</span></div> <div class="e"><span id="5">阿奴</span></div> </body> </html>
from lxml import html etree=html.etree if __name__=="__main__": parser = etree.HTMLParser(encoding="utf-8") #实例化好了一个etree对象,且将被解析的源码加载到了该对象中 tree=etree.parse('a.html',parser=parser) # result=tree.xpath("/html/body/div") # result = tree.xpath("/html//div") result = tree.xpath("//div") print(result)
result = tree.xpath('//div[@class="a"]')#属性定位
result = tree.xpath('//div[@class="a"]/p[2]')#索引定位
result = tree.xpath('//div[@class="b"]//li[1]/a/text()')[0] #获取文本内容
result = tree.xpath('//div[@class="c"]//text()')[0]
result = tree.xpath('//div[@class="a"]//text()') #取文本下的所有文本内容
result = tree.xpath('//div[@class="a"]/img/@src')
3.5.2 xpath实战 -58二手房
网址:北京二手房网,北京房产网,北京二手房买卖出售交易信息-北京58同城
#需求:爬取58二手房中的房源信息 import requests from lxml import html etree=html.etree if __name__=="__main__": #爬取到页面源码数据 url="https://bj.58.com/ershoufang/" headers = { 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.134 Safari/537.36 Edg/103.0.1264.77' } page_text=requests.get(url=url,headers=headers).text #数据解析 tree=etree.HTML(page_text) # print(page_text) list=tree.xpath('//section[@class="list"]/div[@class="property"]') fp=open('58.txt','w',encoding='utf-8') for i in list: # print(i) title=i.xpath('./a/div[@class="property-content"]/div[@class="property-content-detail"]/div[@class="property-content-title"]/h3/@title')[0] print(title) fp.write(title+'\n')
3.5.3 4k图片解析下载
网址:4K美女壁纸高清4K美女图片彼岸图网 (netbian.com)
#需求:解析下载图片数据 https://pic.netbian.com/4kmeinv/ import requests import os from lxml import html etree=html.etree if __name__=="__main__": #爬取到页面源码数据 url="https://pic.netbian.com/4kmeinv/" headers = { 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.134 Safari/537.36 Edg/103.0.1264.77' } response=requests.get(url=url,headers=headers) # response.encoding='utf-8' page_text=response.text #数据解析:src的属性值 tree=etree.HTML(page_text) # print(page_text) list=tree.xpath('//div[@class="slist"]//li') # fp=open('58.txt','w',encoding='utf-8') #创建一个文件夹 if not os.path.exists('./picLibs'): os.mkdir('./picLibs') for i in list: # print(i) img_src="https://pic.netbian.com"+i.xpath('./a/img/@src')[0] img_name=i.xpath('./a/img/@alt')[0]+'.jpg' #通用处理中文乱码的解决方案 img_name=img_name.encode('iso-8859-1').decode('gbk') print(img_name,img_src) # fp.write(title+'\n') #请求图片进行持久化存储 img_data=requests.get(url=img_src,headers=headers).content img_path='picLibs/'+img_name with open(img_path,'wb') as fp: fp.write(img_data) print(img_name+"下载成功!!!")
3.5.4 全国城市名称爬取
网址:PM2.5历史数据_空气质量指数历史数据_中国空气质量在线监测分析平台历史数据
这里采用ctrl +F8 解决debug
方案一:
#需求:解析出所有城市名称 import requests import os from lxml import etree # etree=html.etree if __name__=="__main__": #爬取到页面源码数据 url="https://www.aqistudy.cn/historydata/" headers = { 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.134 Safari/537.36 Edg/103.0.1264.77' } response=requests.get(url=url,headers=headers) # response.encoding='utf-8' page_text=response.text tree=etree.HTML(page_text) host_list=tree.xpath('//div[@class="bottom"]//li') all_city_names=set() for i in host_list: # print(i) host_city_name=i.xpath('./a/text()')[0] all_city_names.add(host_city_name) city_names_list=tree.xpath('//div[@class="bottom"]/ul/div[2]/li') for li in city_names_list: city_name=li.xpath('./a/text()')[0] all_city_names.add(city_name) print(all_city_names,len(all_city_names))
方案二:
#需求:解析出所有城市名称 import requests import os from lxml import etree # etree=html.etree if __name__=="__main__": #爬取到页面源码数据 url="https://www.aqistudy.cn/historydata/" headers = { 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.134 Safari/537.36 Edg/103.0.1264.77' } response=requests.get(url=url,headers=headers) # response.encoding='utf-8' page_text=response.text all_city_names=set() tree=etree.HTML(page_text) #解析到热门城市和所有城市对应的a标签 # //div[@class="bottom"]/ul/li/a 热门城市a标签的层级关系 # //div[@class="bottom"]/ul/div[2]/li 全部城市a标签的层级关系 a_list=tree.xpath('//div[@class="bottom"]/ul/li/a | //div[@class="bottom"]/ul/div[2]/li/a') for a in a_list: city_name=a.xpath('./text()')[0] all_city_names.add(city_name) print(all_city_names,len(all_city_names))
四.反爬初体验
4.1 验证码识别简介
4.1.1 验证码和爬虫之间的联系
反爬机制:验证码。识别验证码图片中的数据,用于模拟登录操作。
识别验证码的操作:
-人工肉眼识别(不推荐)
-第三方自动识别
五.requests进阶概述
我们之前的爬虫中起始已经使用过headers,headers为HTTP协议中得到请求头,一般存放一些和请求内容无关的数据,有时也会存放一些安全验证信息,比如常见的User-Agent,token,cookie等
通过request发送的请求,我们可以把请求头信息放在headers中,也可以单独进行存放,最终由requess自动帮我们拼接成完整的http请求头。
5.1 处理cookie登录小说网
#登录 ->得到cookie #带着cookie 去请求到书架url ->书架上的内容 #必须把上面的两个操作连起来 #我们可以使用session进行请求 ->session你可以认为是一连串的请求,在这个过程中cookie不会丢失 import requests #会话 session=requests.session() #1. 登录 post_url='https://passport.17k.com/ck/user/login' data={ 'loginName':'18264971932', 'password':'123321abc' } headers = { 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.134 Safari/537.36 Edg/103.0.1264.77' } response=session.post(url=post_url,data=data,headers=headers) # print(response.text) # print(response.cookies) #看cookie #2. 拿书架上的数据 url='https://user.17k.com/ck/author/shelf?page=1&appKey=2406394919' response1=session.get(url=url,headers=headers).json() print(response1)
5.2 防盗链 抓取梨视频
页面源代码中的网址和接口中的网址不一致
网址区别:
https://video.pearvideo.com/mp4/short/20161209/cont-1015013-10085660-hd.mp4
https://video.pearvideo.com/mp4/short/20161209/1659833692645-10085660-hd.mp4
200秒骑兵史:气吞万里如虎_动历史-梨视频官网-Pear Video
将抓取的网址进行替换更改
防盗链:Referer
import requests if __name__=='__main__': url='https://pearvideo.com/video_1015013' contId = url.split("_")[1] headers={ 'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36', #防盗链:溯源, 'Referer':url } videoStatus=f'https://pearvideo.com/videoStatus.jsp?contId={contId}&mrd=0.4600743088042203' response=requests.get(url=videoStatus,headers=headers) dic=response.json() srcUrl=dic['videoInfo']['videos']['srcUrl'] systemTime=dic['systemTime'] srcUrl=srcUrl.replace(systemTime,f"cont-{contId}") #下载视频 with open(systemTime+".mp4",mode="wb") as fp: fp.write(requests.get(url=srcUrl,headers=headers).content)
5.3 代理
原理:通过第三方去发送请求
代理网站:站大爷 - 企业级高品质Http代理IP_Socks5代理服务器_免费代理IP
import requests if __name__=='__main__': url='https://www.baidu.com' proxies={ 'https':'https://代理ip网址' # 'https':'https://218.60.8.83:3129' } headers={ 'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36' } response=requests.get(url=url,headers=headers,proxies=proxies) response.encoding='utf-8' print(response.text)
5.4 抓取网易云评论信息
可以观察使用接口时调用的js文件,从下往上执行,最开始的在最下方
最后一个js文件的send数据设置断点寻找目标接口
import requests #pip install pycrypto #这里需要安装 pip install pycryptodome -i http://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com from base64 import b64encode import json from Crypto.Cipher import AES if __name__=='__main__': #1.找到未加密的参数 #var bKB5G = window.asrsea(JSON.stringify(i2x), buV2x(["流泪", "强"]), buV2x(Rg8Y.md), buV2x(["爱心", "女孩", "惊恐", "大笑"]));加密语法 #2.想办法把参数进行加密(必须参考网易的逻辑)param ->encText ,encSecKey->encSecKey #3.请求到网易,拿到评论信息 url='https://music.163.com/weapi/comment/resource/comments/get?csrf_token=' #请求方式是POST headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36' } data={ 'csrf_token': "", 'cursor': "-1", 'offset': "0", 'orderType': "1", 'pageNo': "1", 'pageSize': "20", 'rid': "R_SO_4_368794", 'threadId': "R_SO_4_368794" } # "rid=R_SO_4_368794&threadId=R_SO_4_368794&pageNo=1&pageSize=20&cursor=-1&offset=0&orderType=1" print(json.dumps(data)) e = "010001" f = "00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7" g = "0CoJUm6Qyw8W8jud" i = "WsXy0BKCyb9uStPE" # 定死i 以拿到以定死的i的encSecKey 不然它变化 def get_encSecKey(): # return "091870ee1d9eaa44f50d8788f77f9c625cafc76c4ade76499875831f8b3ded2f417e4909cb47d8c97bfe5e9eab0466b265e1ad2d96beb0a392f3c54394171f9caba249b01c76630b4c98a63f17236ee783c370a7ff48a6cc7417972afe09a0811027f61bd5c9179deb7174d215e6c3896dc33792d79540b835721ab3e0a95ab0" return "2331db0c4f71b25b3ff19e347294e2a75b4bd3b1d7a991f7b9d7feeec08f8cdbd1e653e3205fe7867a2171d2c373113b4aa3920a34afef505cc3f912543cefdc65f13deace2db212ea3353f84d503ceb5a1f4c0474749eb712b2d20760d9b1b3a3b4e57d6787e3e254ddb53dd6fe6734aad97f518a81bb20d1b205c95fca3b2a" def get_params(data): # 默认收到的是字符串,并非字典 first = enc_params(data, g) second = enc_params(first, i) return second #转化成16倍数 def to_16(data): pad = 16 - len(data) % 16 # print("pad: %d , data : %s" %(pad,data)) data += chr(pad) * pad return data #把参数进行加密 def enc_params(data, key): iv="0102030405060708" data=to_16(data) aes = AES.new(key=key.encode("utf-8"), IV=iv.encode("utf-8"), mode=AES.MODE_CBC) #创造加密器 bs0=aes.encrypt(data.encode("utf-8")) #加密,加密对的内容的长度必须是16的倍数 return str(b64encode(bs0), "utf-8") #处理加密过程 ''' 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); #取字符串的某某位置 return c } function b(a, b) { #a时要加密的内容 var c = CryptoJS.enc.Utf8.parse(b) #b是密钥 , d = CryptoJS.enc.Utf8.parse("0102030405060708") , e = CryptoJS.enc.Utf8.parse(a) #e是数据 , f = CryptoJS.AES.encrypt(e, c, { #AES加密 c是加密的密钥 iv: d, #偏移量 mode: CryptoJS.mode.CBC # 模式采用CBC }); return f.toString() } g="0CoJUm6Qyw8W8jud" e="010001" f="00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7" 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:数据 e:buV2x(["流泪", "强"]) ==010001 f: buV2x(Rg8Y.md)==f g = 0CoJUm6Qyw8W8jud var h = {} # 空对象 , i = a(16); # i就是一个16位的随机值,把i设置成定值,得到的key是一个定值 return h.encText = b(d, g), #g 是密钥 h.encText = b(h.encText, i), #返回的就是params i也是密钥 h.encSecKey = c(i, e, f), #得到的就是enSecKey ,e和f是定值 h } function e(a, b, d, e) { var f = {}; return f.encText = c(a + e, b, d), f } 两次加密: 数据+g =>b => 第一次加密 +i => b =params ''' resp = requests.post(url, data={ "params": get_params(json.dumps(data)), "encSecKey": get_encSecKey() }) print(resp.status_code) print(resp.text)
六.异步爬虫
到目前为止,我们可以解决爬虫的基本抓取流程,但是抓取效率还是不够高,如何提高抓取效率?我们可以选择多线程,多进程、协程等操作完成异步爬虫。
6.1 多线程
第一套写法
#多线程 from threading import Thread #线程类 def func(): for i in range(1000): print("func",i) if __name__ == '__main__': t = Thread(target=func()) #创建线程并给线程安排任务 t.start() #多线程状态为可以开始工作状态,具体的执行时间由CPU决定 for i in range(1000): print("main",i)
第二套写法
#多线程 from threading import Thread #线程类 class MyThread(Thread): def run(self): #当线程被执行的时候,被执行的就是run() for i in range(1000): print("子线程",i) if __name__ == '__main__': t=MyThread() t.start() for i in range(1000): print("主线程",i)
6.2 多进程
#多进程 from multiprocessing import Process def func(): for i in range(1000): print("子进程",i) if __name__ == '__main__': p=Process(target=func) p.start() for i in range(1000): print("主进程",i)
6.2.1 多线程获取线程名字
#多线程 from threading import Thread #线程类 def func(name): for i in range(1000): print(name,i) if __name__ == '__main__': t1 = Thread(target=func,args=("胡歌",)) #传递参数必须是元组 t1.start() t2 =Thread(target=func,args=("李逍遥",)) t2.start()
6.3 线程池和进程池入门(适当的使用)
好处:我们可以降低系统对进程或者线程创建和销毁的一个频率,从而很好的降低系统的开销。
弊端:池中线程和进程的数量是有上限。
6.3.1线程池的基本使用
单线程串行方式执行
import time #使用单线程串行方式执行 def get_page(str): print("正在下载:",str) time.sleep(2) print("下载成功:",str) name_list=['xiaozi','aa','bb','cc'] start_time =time.time() for i in range(len(name_list)): get_page(name_list[i]) end_time=time.time() print("%d second" %(end_time-start_time))
线程池使用
import time #导入线程池对应的类 from multiprocessing.dummy import Pool #使用线程池方式执行 start_time =time.time() def get_page(str): print("正在下载:",str) time.sleep(2) print("下载成功:",str) name_list=['xiaozi','aa','bb','cc'] #实例化一个线程池对象 pool =Pool(4) #将列表中每一个列表元素传递给get_page进行处理 pool.map(get_page,name_list) end_time=time.time() print("%d second" %(end_time-start_time))
6.3.2 线程池爬取梨视频的视频数据
网址:万象热点资讯_万象热点新闻-梨视频官网-Pear Video
抓取万象中的热点视频,结合之前的防盗链
import requests from lxml import etree from multiprocessing.dummy import Pool #原则:线程池处理的是阻塞且耗时的操作 if __name__=='__main__': domain="https://pearvideo.com" first_url='https://pearvideo.com/panorama' # url='https://pearvideo.com/video_1015013' # contId = url.split("_")[1] headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36' } page_text=requests.get(url=first_url,headers=headers).text tree=etree.HTML(page_text) li_list=tree.xpath('//ul[@id="listvideoListUl"]/li') urls=[] for li in li_list: detail_url=domain+li.xpath('./div/a/@href')[0] headers1 = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36', 'Referer': detail_url } name=li.xpath('./div/a/div[2]/text()')[0]+".mp4" print(detail_url,name) contId = detail_url.split("_")[1] print(contId) detail_url='https://pearvideo.com/video_'+contId videoStatus = f'https://pearvideo.com/videoStatus.jsp?contId={contId}&mrd=0.4600743088042203' response = requests.get(url=videoStatus, headers=headers1) dic = response.json() srcUrl = dic['videoInfo']['videos']['srcUrl'] systemTime = dic['systemTime'] srcUrl = srcUrl.replace(systemTime, f"cont-{contId}") dic={ 'name':name, 'url':srcUrl, 'Referer':detail_url } urls.append(dic) print(urls) def get_video_data(dic): url=dic['url'] print(dic['name'],'正在下载') newheaders={ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36', 'Referer':dic['Referer'] } data=requests.get(url=url,headers=newheaders).content #持久化存储操作 with open(dic['name'], mode="wb") as fp: fp.write(data) print(dic['name'],'下载成功') #使用线程池对视频数据进行抓取 pool=Pool(5) pool.map(get_video_data,urls) pool.close() pool.join()
6.4 协程
6.4.1 协程的基本概念
import time def func(): print("胡歌") time.sleep(3) #让当前的线程处于阻塞状态。cpu不服务于当前线程 print("地瓜") if __name__ == '__main__': func() #input()程序也是处于阻塞状态 #request.get(url)在网络请求返回数据之前,程序也是处于阻塞状态 #一般情况下,当程序处于IO操作的时候。线程将会处于阻塞状态 #协程:当程序遇见了IO操作的时候,可以选择的切换到其他任务上 #在微观上是一个任务一个任务的进行切换,切换条件一般就是IO操作 #在宏观上,我们能看到的其实是多个任务一起执行 #多任务异步操作 #上方所讲的一切,都是在单线程的条件下
6.4.2 多任务异步协程
6.4.2.1 示例
import time import asyncio async def func(): print("胡歌") time.sleep(3) #让当前的线程处于阻塞状态。cpu不服务于当前线程 print("地瓜") if __name__ == '__main__': g=func() #此时的函数是异步协程函数,此时函数执行得到的是一个协程对象 print(g) asyncio.run(g) #协程程序运行需要asyncio模块的支持
6.4.2.2 错误写法
import time import asyncio async def func1(): print("胡歌A") # time.sleep(3) #当程序出现了同步操作的时候,异步就中断了 await asyncio.sleep(3) #异步操作的代码 print("地瓜") async def func2(): print("胡歌B") # time.sleep(2) #让当前的线程处于阻塞状态。cpu不服务于当前线程 await asyncio.sleep(2) # 异步操作的代码 print("李逍遥") async def func3(): print("胡歌C") # time.sleep(4) #让当前的线程处于阻塞状态。cpu不服务于当前线程 await asyncio.sleep(4) # 异步操作的代码 print("杨延昭") async def func4(): print("胡歌D") await asyncio.sleep(5) # 异步操作的代码 # time.sleep(5) #让当前的线程处于阻塞状态。cpu不服务于当前线程 print("景天") if __name__ == '__main__': f1=func1() f2=func2() f3=func3() f4=func4() task=[ f1,f2,f3,f4 ] t1=time.time() #一次性启动多个任务(协程) asyncio.run(asyncio.wait(task)) t2=time.time() print(t2-t1)
6.4.2.3 正确写法
import time import asyncio async def func1(): print("胡歌A") # time.sleep(3) #当程序出现了同步操作的时候,异步就中断了 await asyncio.sleep(3) #异步操作的代码 print("地瓜") async def func2(): print("胡歌B") # time.sleep(2) #让当前的线程处于阻塞状态。cpu不服务于当前线程 await asyncio.sleep(2) # 异步操作的代码 print("李逍遥") async def func3(): print("胡歌C") # time.sleep(4) #让当前的线程处于阻塞状态。cpu不服务于当前线程 await asyncio.sleep(4) # 异步操作的代码 print("杨延昭") async def func4(): print("胡歌D") await asyncio.sleep(5) # 异步操作的代码 # time.sleep(5) #让当前的线程处于阻塞状态。cpu不服务于当前线程 print("景天") async def main(): # #第一种写法 # f1=func1() # await f1 #一般await挂起操作放在协程对象前面 #第二种写法(推荐) tasks=[ func1(), func2(), func3(), func4() ] await asyncio.wait(tasks) if __name__ == '__main__': t1=time.time() asyncio.run(main()) t2=time.time() print(t2-t1)
6.4.2.4 在爬虫中的模板
#在爬虫领域的应用 async def download(url): print("准备开始下载") await asyncio.sleep(2) #网络请求 print("下载完成") async def main(): urls=[ "https://www.baidu.com", "https://www.bilibili.com", "https://www.163.com" ] tasks=[] for url in urls: d=download(url) tasks.append(d) await asyncio.wait(tasks) if __name__ == '__main__': asyncio.run(main())
6.4.2.5 异步协程过时警告
需要将放在tasks里的协程对象封装成tasks对象
async def main(): # #第一种写法 # f1=func1() # await f1 #一般await挂起操作放在协程对象前面 #第二种写法(推荐) tasks=[ #在python3.8 之后进行这种方案处理 asyncio.create_task(func1()), asyncio.create_task(func2()), asyncio.create_task(func3()), asyncio.create_task(func4()) ] await asyncio.wait(tasks)
6.5 异步http请求aiohttp模块
下载图片 aiofile
# request.get() 同步的代码 ->异步操作 # pip install aiohttp -i http://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com import asyncio import aiohttp urls=[ "http://kr.shanghai-jiuxin.com/file/mm/20211130/zkc0inje5x0.jpg", "http://kr.shanghai-jiuxin.com/file/mm/20211129/45b0pc0wuz2.jpg", "http://kr.shanghai-jiuxin.com/file/mm/20211129/4crz35ldti5.jpg" ] async def aiodownload(url): name=url.rsplit("/",1)[1] async with aiohttp.ClientSession() as session: async with session.get(url) as response: #请求回来数据,写入文件 with open(name,mode='wb') as f: f.write(await response.content.read()) #读取内容是异步的需要挂起 #response.content.read() #等价于response.content #response.text() 文本数据 #response.json() print(name,"下载完成") #aiohttp.ClientSession() #等价于requests模块 #发送请求 #保存图片内容 #保存到文件 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())
6.6 异步爬虫实战 -扒光一部小说(百度小说西游记整本)
''' 1.同步操作:访问getCatlog 拿到所有的章节的cid和名称 2.异步操作:访问getChapterContent 下载所有的文章内容 ''' import requests import asyncio import json import aiohttp import aiofiles headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36' } async def aiodownload(cid,b_id,title): data={ "book_id":b_id, "cid":f"{b_id}|{cid}", "need_bookinfo":1 } data=json.dumps(data) url=f"https://dushu.baidu.com/api/pc/getChapterContent?data={data}" async with aiohttp.ClientSession() as session: async with session.get(url) as response: dic=await response.json() async with aiofiles.open(title,mode='w',encoding="utf-8") as fp: await fp.write(dic['data']['novel']['content']) #把小说内容写出 async def getCatlog(url): response=requests.get(url=url,headers=headers) dic=response.json() tasks=[] for item in dic['data']['novel']['items']: #item 就是每一个章节要获取的cid title =item['title'] cid=item['cid'] tasks.append(asyncio.create_task(aiodownload(cid,b_id,title))) print(cid,title) await asyncio.wait(tasks) if __name__ == '__main__': b_id="4306063500" url='https://dushu.baidu.com/api/pc/getCatalog?data={"book_id":'+b_id+'}' asyncio.run(getCatlog(url))
6.7 视频网站的工作原理
#<video src="url"></video> #一般的视频网站是怎么做的? #用户上传 ->转码(把视频做处理,2k,1080,标清) ->切片处理(把单个的文件进行拆分) 60 #用户在进行拉动进度条的时候 #==================================== 进度条 #需要一个文件记录:1.视频播放顺序,2.视频存放的路径 #M3U txt json 文本 #想要抓取一个视频: # 1. 找到m3u8(各种手段) # 2.通过m3u8下载到ts文件 # 3.可以通过各种手段(不仅是编程手段) 把ts文件合并为一个mp4文件
6.8 抓取看剧柒柒(幸福到万家)
网址:幸福到万家详情介绍-幸福到万家在线观看-幸福到万家迅雷下载 - 看剧柒柒-77影视-77电影-看剧77-77影视-影视大全免费追剧-柒柒看剧 (kanju77.com)
找iframe的url
''' 流程: 1.拿到主页的源代码,找到iframe 2.从iframe的页面源代码中拿到m3u8文件 3.下载m3u8 4.读取m3u8文件,下载视频 5.解码 6.合并视频 ''' import asyncio import aiofiles import aiohttp import requests from Crypto.Cipher import AES import time domain="https://s7.fsvod1.com" headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36' } async def download_ts(url,name,session): print(f"{name}开始下载") async with session.get(url) as resp: await asyncio.sleep(0.5) async with aiofiles.open(f"video/{name}",mode="wb") as fp: await fp.write(await resp.content.read())#把下载到的内容写入到文件中 print(f"{name}下载完毕") def get_key(url): res=requests.get(url) return res.text.encode('utf-8') def download_m3u8(url,name): res=requests.get(url,headers) time.sleep(0.5) with open(name,mode="wb") as fp: fp.write(res.content) async def dec_ts(name,key): aes=AES.new(key=key,IV=b"0000000000000000",mode=AES.MODE_CBC) async with aiofiles.open(f"video/{name}",mode="rb") as f1,\ aiofiles.open(f"video2/temp_{name}",mode="wb") as f2: bs=await f1.read() #从源文件读取内容 await f2.write(aes.decrypt(bs)) #把解密好的文件写入 print(f"{name}处理完毕") async def aio_dec(key): #解密 tasks=[] async with aiofiles.open("幸福到万家_second_m3u8.txt",mode="r",encoding="utf-8") as fp: count = 10000 async for line in fp: if line.startswith("#"): continue line=line.strip() count += 1 name = str(count) + ".ts" # line = line.rsplit("hls/")[-1] #开始创建异步任务 task=asyncio.create_task(dec_ts(name,key)) tasks.append(task) await asyncio.wait(tasks) async def aio_download(): tasks=[] # timeout = aiohttp.ClientTimeout(total=10) # 将超时时间设置为600秒 connector = aiohttp.TCPConnector(limit=42) # 将并发数量降低 async with aiohttp.ClientSession(connector=connector) as session:#提前准备好session async with aiofiles.open("幸福到万家_second_m3u8.txt",mode='r',encoding='utf-8') as fp: count=10000 async for line in fp: if line.startswith("#"): continue line=line.strip() #去掉没用的空格和换行 ts_url=domain+line # print(ts_url) line=line.rsplit("hls/")[-1] count+=1 name=str(count)+".ts" task=asyncio.create_task(download_ts(ts_url,name,session)) #创建任务 tasks.append(task) await asyncio.wait(tasks) #等待任务结束 def main(): # 1.拿到主页面的页面源代码,找到iframe对应的url # iframe_src=get_iframe_src(url) #拿到m3u8文件的地址 url="https://s7.fsvod1.com/20220629/JVzrefWe/index.m3u8" #下载m3u8文件 download_m3u8(url,"幸福到万家_first_m3u8.txt") #下载第二层m3u8文件 with open("幸福到万家_first_m3u8.txt",mode="r",encoding="utf-8") as fp: for line in fp: if line.startswith("#"): continue else: line=line.strip() #去掉空表或者换行符 #拼接第二层m3u8的下载路径 #/20220721/q7b0uDF1/1500kb/hls/index.m3u8 second_m3u8=domain+line # print(second_m3u8) download_m3u8(second_m3u8,"幸福到万家_second_m3u8.txt") #下载视频 #异步协程 loop = asyncio.get_event_loop() loop.run_until_complete(aio_download()) # #拿到密钥 key_url="https://s7.fsvod1.com/20220629/JVzrefWe/1500kb/hls/key.key" key=get_key(key_url) #解密 asyncio.run(aio_dec(key)) # loop.run_until_complete(aio_dec(key)) if __name__ == '__main__': main()
注意问题:aiohttp超时问题 ts文件顺序问题
七. selenium 模块
7.1 selenium模块的引入
#能不能让程序连接到浏览器,让浏览器来完成各种复杂的操作,我们只接受最终的结果 #selenium:自动化测试工具 #可以打开浏览器,然后像人一样操作浏览器 #程序员可以从selenium中直接提取网页上的各种信息 #环境搭建: # pip install selenium -i http://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com #下载浏览器驱动: https://npm.taobao.org/mirrors/chromedriver #把解压缩的浏览器驱动 chromedriver 放在python解释器所在的文件夹 #让selenium启动谷歌浏览器 from selenium.webdriver import Chrome # 1. 创建浏览器对象 web=Chrome() #2.打开一个网址 web.get("http://www.baidu.com") print(web.title)
7.2 selenium各种操作 抓拉钩
网址:互联网求职招聘找工作-上拉勾招聘-专业的互联网求职招聘网站
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"]/ul/li[1]/a') #找到某个元素,点击它 # el=web.find_element_by_xpath('//*[@id="changeCityBox"]/ul/li[1]/a') el.click() #点击事件 time.sleep(1)#让浏览器缓一会 #找到输入框,输入python => 输入回车|点击搜索按钮 web.find_element(By.XPATH,'//*[@id="search_input"]').send_keys("python",Keys.ENTER) time.sleep(1) #查找存放数据的位置,进行数据提取 #找到页面中存放数据所有的div #//*[@id="jobList"]/div[1]/div[1] div_list=web.find_elements(By.XPATH,'//*[@id="jobList"]/div[1]/div') for div in div_list: job_name=div.find_element(By.TAG_NAME,"a").text job_price=div.find_element(By.XPATH,"./div/div/div[2]/span").text company_name=div.find_element(By.XPATH,"./div/div[2]/div/a").text print(job_name,job_price,company_name)
7.3 selenium--各种操作--窗口之间的切换
7.3.1 窗口的切换
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") web.find_element(By.XPATH,'//*[@id="cboxClose"]').click() time.sleep(1) # el=web.find_element(By.XPATH,'//*[@id="changeCityBox"]/ul/li[1]/a') # #找到某个元素,点击它 # # el=web.find_element_by_xpath('//*[@id="changeCityBox"]/ul/li[1]/a') # el.click() #点击事件 # time.sleep(1)#让浏览器缓一会 # #找到输入框,输入python => 输入回车|点击搜索按钮 web.find_element(By.XPATH,'//*[@id="search_input"]').send_keys("python",Keys.ENTER) time.sleep(1) web.find_element(By.XPATH,'//*[@id="jobList"]/div[1]/div[1]/div[1]/div[1]/div[1]/a').click() #如何进入到新窗口进行提取 #注意,在selenium的眼中,新窗口默认是不切换过来的 web.switch_to.window(web.window_handles[-1]) #在新窗口中提取内容 job_detail=web.find_element(By.XPATH,'//*[@id="job_detail"]/dd[2]/div').text print(job_detail) web.close() web.switch_to.window(web.window_handles[0])
7.3.2 获取iframe页面
from selenium.webdriver import Chrome from selenium.webdriver.common.by import By web=Chrome() #如果页面中遇到了 iframe如何处理 web.get("https://kanju77.com/vy/110347-1-1/") #处理iframe的话,必须先拿到iframe,然后切换视角到iframe,再然后才可以拿数据 iframe=web.find_element(By.XPATH,'//*[@id="playleft"]/iframe') web.switch_to.frame(iframe) # web.switch_to.default_content() #切换回原页面 tx=web.find_element(By.XPATH,'/html/head/title').text print(tx)
7.4 selenium--各种操作--无头浏览器
#让浏览器在后台运行 from selenium.webdriver import Chrome from selenium.webdriver.chrome.options import Options from selenium.webdriver.common.by import By from selenium.webdriver.support.select import Select import time #准备好参数配置 opt=Options() opt.add_argument("--headless") opt.add_argument("--disable-gpu") web=Chrome(options=opt) #把参数配置设置到浏览器 #如果页面中遇到了 iframe如何处理 web.get("https://www.fjnu.edu.cn/") #如何拿到页面代码(经过数据加载以及js执行之后的结果的html内容) print(web.page_source) #定义到下拉列表 # sel_el=web.find_element(By.XPATH,'//*[@id="main"]/div[1]/div/div/h2/select') # #对元素进行包装,包装成下拉菜单 # sel=Select(sel_el) # #让浏览器调整选项 # for i in sel.options: #i就是每一个下拉框选项的索引位置 # sel.select_by_value(i) # time.sleep(8) # if(i=='3.x'): # continue # else: # text=web.find_element(By.XPATH,'/html/head/title').text # print(text) # web.close()
7.5 selenium--超级鹰处理验证码(超级鹰干超级鹰)
将超级鹰的demo放置在要使用的目录下
# 1. 图像识别 # 2. 选择互联网上成熟的验证码破解工具 from selenium.webdriver import Chrome from selenium.webdriver.common.by import By import time from chaojiying import Chaojiying_Client web=Chrome() 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('zhy123321', '123321abc', '937631') 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("zhy123321") web.find_element(By.XPATH,'/html/body/div[3]/div/div[3]/div[1]/form/p[2]/input').send_keys('123321abc') web.find_element(By.XPATH,'/html/body/div[3]/div/div[3]/div[1]/form/p[3]/input').send_keys(verify_code) time.sleep(5) #点击登录 web.find_element(By.XPATH,'/html/body/div[3]/div/div[3]/div[1]/form/p[4]/input').click()