目录javascript
启动:jupyter notebook 介绍: anaconda是一个集成环境(数据分析+机器学习) 提供了一个叫作jupyter的可视化工具(基于浏览器) jupyter的基本使用 快捷键: 插入cell:a,b 删除:x 执行:shift+enter 切换cell的模式:y,m tab:自动补全 打开帮助文档:shift+tab
风险所在php
如何规避风险css
爬虫机制 :应用在网站中html
反反爬机制 : 应用在爬虫程序中java
第一个反爬机制 :node
robots协议:纯文本的协议python
通用头域包含请求和响应消息都支持的头域。mysql
Request URL:请求的URL地址 Request Method: 请求方法,get/post/put/…… Status Code:状态码,200 为请求成功 Remote Address:路由地址
1) Accept: 告诉WEB服务器本身接受什么介质类型,*/* 表示任何类型,type/* 表示该类型下的全部子类型; 2)Accept-Charset: 浏览器申明本身接收的字符集 Accept-Encoding:浏览器申明本身接收的编码方法,一般指定压缩方法,是否支持压缩,支持什么压缩方法(gzip, deflate) 3)Accept-Language: 浏览器申明本身接收的语言。语言跟字符集的区别:中文是语言,中文有多种字符集,好比big5,gb2312,gbk等等。 4)Authorization: 当客户端接收到来自WEB服务器的 WWW-Authenticate 响应时,该头部来回应本身的身份验证信息给WEB服务器。 5)Connection:表示是否须要持久链接。close(告诉WEB服务器或者代理服务器,在完成本次请求的响应后,断开链接, 不要等待本次链接的后续请求了)。keep-alive(告诉WEB服务器或者代理服务器,在完成本次请求的响应后,保持链接,等待本次链接的后续请求)。 6)Referer:发送请求页面URL。浏览器向 WEB 服务器代表本身是从哪一个 网页/URL 得到/点击 当前请求中的网址/URL。 7)User-Agent: 浏览器代表本身的身份(是哪一种浏览器)。 8)Host: 发送请求页面所在域。 9)Cache-Control:浏览器应遵循的缓存机制。 no-cache(不要缓存的实体,要求如今从WEB服务器去取) max-age:(只接受 Age 值小于 max-age 值,而且没有过时的对象) max-stale:(能够接受过去的对象,可是过时时间必须小于 max-stale 值) min-fresh:(接受其新鲜生命期大于其当前 Age 跟 min-fresh 值之和的缓存对象) 10)Pramga:主要使用 Pramga: no-cache,至关于 Cache-Control: no-cache。 11)Range:浏览器(好比 Flashget 多线程下载时)告诉 WEB 服务器本身想取对象的哪部分。 12)Form:一种请求头标,给定控制用户代理的人工用户的电子邮件地址。 13)Cookie:这是最重要的请求头信息之一
1)Age:当代理服务器用本身缓存的实体去响应请求时,用该头部代表该实体从产生到如今通过多长时间了。 2)Accept-Ranges:WEB服务器代表本身是否接受获取其某个实体的一部分(好比文件的一部分)的请求。bytes:表示接受,none:表示不接受。 3) Cache-Control:服务器应遵循的缓存机制。 public(能够用 Cached 内容回应任何用户) private(只能用缓存内容回应先前请求该内容的那个用户) no-cache(能够缓存,可是只有在跟WEB服务器验证了其有效后,才能返回给客户端) max-age:(本响应包含的对象的过时时间) ALL: no-store(不容许缓存) 4) Connection: 是否须要持久链接 close(链接已经关闭)。 keepalive(链接保持着,在等待本次链接的后续请求)。 Keep-Alive:若是浏览器请求保持链接,则该头部代表但愿 WEB 服务器保持链接多长时间(秒)。例如:Keep- Alive:300 5)Content-Encoding:WEB服务器代表本身使用了什么压缩方法(gzip,deflate)压缩响应中的对象。 例如:Content-Encoding:gzip 6)Content-Language:WEB 服务器告诉浏览器本身响应的对象的语言。 7)Content-Length:WEB 服务器告诉浏览器本身响应的对象的长度。例如:Content-Length: 26012 8)Content-Range:WEB 服务器代表该响应包含的部分对象为整个对象的哪一个部分。例如:Content-Range: bytes 21010-47021/47022 9)Content-Type:WEB 服务器告诉浏览器本身响应的对象的类型。例如:Content-Type:application/xml 10)Expired:WEB服务器代表该实体将在何时过时,对于过时了的对象,只有在跟WEB服务器验证了其有效性后,才能用来响应客户请求。 11) Last-Modified:WEB 服务器认为对象的最后修改时间,好比文件的最后修改时间,动态页面的最后产生时间等等。 12) Location:WEB 服务器告诉浏览器,试图访问的对象已经被移到别的位置了,到该头部指定的位置去取。 13)Proxy-Authenticate: 代理服务器响应浏览器,要求其提供代理身份验证信息。 14)Server: WEB 服务器代表本身是什么软件及版本等信息。 15)Refresh:表示浏览器应该在多少时间以后刷新文档,以秒计。
对称密钥加密 非对称密钥加密 证书密钥加密
基于网络请求的python模块jquery
做用 :模拟浏览器发送请求,实现爬虫linux
环境安装 : pip install request
编码流程 :
import requests #1.指定url url = 'https://www.sogou.com/' #2.请求发送:get返回的是一个响应对象 response = requests.get(url=url) #3.获取响应数据:text返回的是字符串形式的响应数据 page_text = response.text #4.持久化存储 with open('./sogou.html','w',encoding='utf-8') as fp: fp.write(page_text)
url = 'https://www.sogou.com/web' #请求参数的动态化 wd = input('enter a key word:') params = { 'query':wd } response = requests.get(url=url,params=params) page_text = response.text fileName = wd+'.html' with open(fileName,'w',encoding='utf-8') as fp: fp.write(page_text) print(fileName,'爬取成功!')
上述代码问题:
#乱码问题的解决 url = 'https://www.sogou.com/web' #请求参数的动态化 wd = input('enter a key word:') params = { 'query':wd } response = requests.get(url=url,params=params) #将响应数据的编码格式手动进行指定 response.encoding = 'utf-8' page_text = response.text fileName = wd+'.html' with open(fileName,'w',encoding='utf-8') as fp: fp.write(page_text) print(fileName,'爬取成功!')
#UA假装操做 url = 'https://www.sogou.com/web' #请求参数的动态化 wd = input('enter a key word:') params = { 'query':wd } #UA假装 headers = { 'User-Agent':'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36' } response = requests.get(url=url,params=params,headers=headers) #将响应数据的编码格式手动进行指定 response.encoding = 'utf-8' page_text = response.text fileName = wd+'.html' with open(fileName,'w',encoding='utf-8') as fp: fp.write(page_text) print(fileName,'爬取成功!')
爬取豆瓣电影中动态加载出的电影详情数据 :
url = 'https://movie.douban.com/j/chart/top_list' #参数动态化 params = { 'type': '17', 'interval_id': '100:90', 'action': '', 'start': '0', 'limit': '200', } response = requests.get(url=url,params=params,headers=headers) #json()返回的是序列化好的对象 movie_list = response.json() for movie in movie_list: print(movie['title'],movie['score'])
总结:对一个陌生的网站进行数据爬取的时候,首先肯定的一点就是爬取的数据是否为动态加载出来的 是:须要经过抓包工具捕获到动态加载数据对应的数据包,从中提取出url和请求参数。 不是:直接对浏览器地址栏的url发起请求便可 如何检测爬取的数据是否是动态加载出来的? 经过抓包工具进行局部搜索就能够验证数据是否为动态加载 搜索到:不是动态加载 搜索不到:是动态加载 如何定位动态加载的数据在哪呢? 经过抓包工具进行全局搜索进行定位
http://www.kfc.com.cn/kfccda/storelist/index.aspx
url = 'http://www.kfc.com.cn/kfccda/ashx/GetStoreList.ashx?op=keyword' data = { 'cname': '', 'pid': '', 'keyword': '上海', 'pageIndex': '1', 'pageSize': '10', } address_dic = requests.post(url=url,data=data,headers=headers).json() for dic in address_dic['Table1']: print(dic['addressDetail'])
- 需求 https://www.fjggfw.gov.cn/Website/JYXXNew.aspx 福建省公共资源交易中心 提取内容: 工程建设中的中标结果信息/中标候选人信息 1. 完整的html中标信息 2. 第一中标候选人 3. 中标金额 4. 中标时间 5. 其它参与投标的公司
- 实现思路 - 确认爬取的数据都是动态加载出来的 - 在首页中捕获到ajax请求对应的数据包,从该数据包中提取出请求的url和请求参数 - 对提取到的url进行请求发送,获取响应数据(json) - 从json串中提取到每个公告对应的id值 - 将id值和中标信息对应的url进行整合,进行请求发送捕获到每个公告对应的中标信息数据
post_url = 'https://www.fjggfw.gov.cn/Website/AjaxHandler/BuilderHandler.ashx' headers = { 'User-Agent':'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36', 'Cookie': '_qddac=4-3-1.4euvh3.x4wulp.k1hj8mnw; ASP.NET_SessionId=o4xkycpib3ry5rzkvfcamxzk; Hm_lvt_94bfa5b89a33cebfead2f88d38657023=1570520304; __root_domain_v=.fjggfw.gov.cn; _qddaz=QD.89mfu7.7kgq8w.k1hj8mhg; _qdda=4-1.4euvh3; _qddab=4-x4wulp.k1hj8mnw; _qddamta_2852155767=4-0; _qddagsx_02095bad0b=2882f90558bd014d97adf2d81c54875229141367446ccfed2b0c8913707c606ccf30ec99a338fed545821a5ff0476fd6332b8721c380e9dfb75dcc00600350b31d85d17d284bb5d6713a887ee73fa35c32b7350c9909379a8d9f728ac0c902e470cb5894c901c4176ada8a81e2ae1a7348ae5da6ff97dfb43a23c6c46ec8ec10; Hm_lpvt_94bfa5b89a33cebfead2f88d38657023=1570520973' } data = { 'OPtype': 'GetListNew', 'pageNo': '1', 'pageSize': '10', 'proArea': '-1', 'category': 'GCJS', 'announcementType': '-1', 'ProType': '-1', 'xmlx': '-1', 'projectName': '', 'TopTime': '2019-07-10 00:00:00', 'EndTime': '2019-10-08 23:59:59', 'rrr': '0.7293828344656237', } post_data = requests.post(url=post_url,headers=headers,data=data).json() for dic in post_data['data']: _id = int(dic['M_ID']) detail_url = 'https://www.fjggfw.gov.cn/Website/AjaxHandler/BuilderHandler.ashx?OPtype=GetGGInfoPC&ID={}&GGTYPE=5&url=AjaxHandler%2FBuilderHandler.ashx'.format(_id) company_data = requests.get(url=detail_url,headers=headers).json()['data'] company_str = ''.join(company_data) print(company_str)
- 基于requests| - 基于urllib - 区别:urllib中的urlretrieve不能够进行UA假装
import requests headers = { 'User-Agent':'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36' }
#基于requests的图片爬取 url = 'http://tva1.sinaimg.cn/mw600/007QUzsKgy1g7qzr59hk7j30cs0gxn82.jpg' img_data = requests.get(url=url,headers=headers).content #content返回的是byte类型的响应数据 with open('./123.jpg','wb') as fp: fp.write(img_data)
#基于urllib的图片爬取 from urllib import request url = 'http://tva1.sinaimg.cn/mw600/007QUzsKgy1g7qzr59hk7j30cs0gxn82.jpg' request.urlretrieve(url,'./456.jpg')
概念:将一整张页面中的局部数据进行提取/解析
做用:用来实现聚焦爬虫的吧
实现方式:
数据解析的通用原理是什么?
页面中的相关的字符串的数据都存储在哪里呢?
- 基于聚焦爬虫的编码流程 - 指定url - 发起请求 - 获取响应数据 - 数据解析 - 持久化存储
- 将煎蛋网中的图片数据进行爬取且存储在本地 :
import re import os dirName = './imgLibs' if not os.path.exists(dirName): os.mkdir(dirName) url = 'http://jandan.net/pic/MjAxOTEwMDktNjY=#comments' page_text = requests.get(url,headers=headers).text #解析数据:img标签的src的属性值 ex = '<div class="text">.*?<img src="(.*?)" referrerPolicy.*?</div>' img_src_list = re.findall(ex,page_text,re.S) for src in img_src_list: if 'org_src' in src: src = re.findall('org_src="(.*?)" onload',src)[0] src = 'http:'+src imgName = src.split('/')[-1] imgPath = dirName+'/'+imgName request.urlretrieve(src,imgPath) print(imgName,'下载成功!!!')
- 环境的安装: - pip install bs4 - pip install lxml - bs4的解析原理: - 实例化一个BeautifulSoup的一个对象,把即将被解析的页面源码数据加载到该对象中 - 须要调用BeautifulSoup对象中的相关的方法和属性进行标签订位和数据的提取 - BeautifulSoup的实例化 - BeautifulSoup(fp,'lxml'):将本地存储的html文档中的页面源码数据加载到该对象中 - BeautifulSoup(page_text,'lxml'):将从互联网中请求道的页面源码数据加载到改对象中 - 标签的定位 - soup.tagName:只能够定位到第一个tagName标签 - 属性定位:soup.find('tagName',attrName='value'),只能够定位到符合要求的第一个标签 - findAll:返回值是一个列表。能够定位到符合要求的全部标签 - 选择器定位:soup.select('选择器') - 选择器:id,class,tag,层级选择器(大于号表示一个层级,空格表示多个层级) - 取文本 - text:将标签中全部的文本取出 - string:将标签中直系的文本取出 - 取属性 - tag['attrName']
from bs4 import BeautifulSoup fp = open('./test.html',encoding='utf-8') soup = BeautifulSoup(fp,'lxml') # soup.div # soup.find('div',class_='song') # soup.findAll('div',class_='song') # soup.select('#feng')[0] # soup.select('.tang > ul > li > a') # soup.select('.tang a') # tag = soup.b # tag.string # div_tag = soup.find('div',class_='tang') # div_tag.text a_tag = soup.select('#feng')[0] a_tag
- 使用bs4解析三国演义小说的标题和内容,存储到本地 :
main_url = 'http://www.shicimingju.com/book/sanguoyanyi.html' page_text = requests.get(url=main_url,headers=headers).text #数据解析:章节的标题和详情页的url soup = BeautifulSoup(page_text,'lxml') a_list = soup.select('.book-mulu > ul > li > a') fp = open('./sanguo.txt','w',encoding='utf-8') for a in a_list: title = a.string detail_url = 'http://www.shicimingju.com'+a['href'] detail_page_text = requests.get(url=detail_url,headers=headers).text #数据解析:章节内容 detail_soup = BeautifulSoup(detail_page_text,'lxml') div_tag = detail_soup.find('div',class_='chapter_content') content = div_tag.text fp.write(title+':'+content+'\n') print(title,'写入成功!!!') fp.close()
- 环境的安装 - pip install lxml - 解析原理 - 实例化一个etree的对象,且把即将被解析的页面源码数据加载到该对象中 - 调用etree对象中的xpath方法结合这不一样形式的xpath表达式进行标签订位和数据提取 - etree对象的实例化 - etree.parse('fileName') - 本地文档 - etree.HTML(page_text) - 网络请求 - 标签订位 - 最左侧的/:必定要从根标签开始进行标签订位 - 非最左侧的/:表示一个层级 - 最左侧的//:能够从任意位置进行指定标签的定位 - 非最左侧的//:表示多个层级 - 属性定位://tagName[@attrName="value"] - 索引定位://tagName[@attrName="value"]/li[2],索引是从1开始 - 逻辑运算: - 找到href属性值为空且class属性值为du的a标签 - //a[@href="" and @class="du"] - 模糊匹配: - //div[contains(@class, "ng")] - //div[starts-with(@class, "ta")] - 取文本 - /text():直系的文本内容 - //text():全部的文本内容 - 取属性 - /@attrName
from lxml import etree tree = etree.parse('./test.html') # tree.xpath('/html//title') # tree.xpath('//div') # tree.xpath('//div[@class="tang"]') # tree.xpath('//div[@class="tang"]/ul/li[2]') # tree.xpath('//p[1]/text()') # tree.xpath('//div[@class="song"]//text()') tree.xpath('//img/@src')[0]
url = 'https://www.huya.com/g/xingxiu' page_text = requests.get(url=url,headers=headers).text #数据解析 tree = etree.HTML(page_text) li_list = tree.xpath('//div[@class="box-bd"]/ul/li') for li in li_list: #实现的是页面局部数据的指定数据的解析 title = li.xpath('./a[2]/text()')[0] author = li.xpath('./span/span[1]/i/text()')[0] hot = li.xpath('./span/span[2]/i[2]/text()')[0] print(title,author,hot)
# url = 'http://pic.netbian.com/4kmeinv/' #第一页 #指定一个通用的url模板:不可变的 url = 'http://pic.netbian.com/4kmeinv/index_%d.html' dirName = './MZLib' if not os.path.exists(dirName): os.mkdir(dirName) for page in range(1,6): if page == 1: new_url = 'http://pic.netbian.com/4kmeinv/' else: new_url = format(url%page) page_text = requests.get(url=new_url,headers=headers).text #数据解析:图片地址&图片名称 tree = etree.HTML(page_text) li_list = tree.xpath('//div[@class="slist"]/ul/li') for li in li_list: img_name = li.xpath('./a/img/@alt')[0] img_name = img_name.encode('iso-8859-1').decode('gbk')+'.jpg' img_src = 'http://pic.netbian.com'+li.xpath('./a/img/@src')[0] img_data = requests.get(img_src,headers=headers).content #图片的二进制类型数据 img_path = dirName+'/'+img_name with open(img_path,'wb') as fp: fp.write(img_data) print('第{}页爬取完毕!!!'.format(page))
url = 'https://www.aqistudy.cn/historydata/' page_text = requests.get(url,headers=headers).text tree = etree.HTML(page_text) # hot_cities = tree.xpath('//div[@class="bottom"]/ul/li/a/text()') # all_cities = tree.xpath('//div[@class="bottom"]/ul/div[2]/li/a/text()') tree.xpath('//div[@class="bottom"]/ul/div[2]/li/a/text() | //div[@class="bottom"]/ul/li/a/text()')
代理指的就是代理服务器 代理的做用 : 请求和响应数据的转发 代理和爬虫之间的关联 : 能够基于代理实现更换爬虫程序请求的ip地址 代理网站 : 1. 西祠 https://www.xicidaili.com/nn/ 2. 快代理 3. www.goubanjia.comm 4. 代理精灵 http://http.zhiliandaili.cn/ 代理的匿名度 : 高匿 : 所访问的服务器察觉不到是不是代理访问,也没法知晓真正访问的ip 匿名 : 所访问的服务器知道是代理访问,但没法查到真正的ip 透明 : 知道是代理,而且知道真实ip 类型 : http https
# 使用代理发请求 import requests headers = { 'User-Agent':'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36', 'Connection':'close' } url = 'https://www.baidu.com/s?ie=UTF-8&wd=ip' page_text = requests.get(url,headers=headers,proxies={'https':'125.87.99.237:22007'}).text with open('./ip.html','w',encoding='utf-8') as fp: fp.write(page_text)
#构建一个付费的代理池 import random ips_pool = [] url = 'http://ip.11jsq.com/index.php/api/entry?method=proxyServer.generate_api_url&packid=1&fa=0&fetch_key=&groupid=0&qty=103&time=1&pro=&city=&port=1&format=html&ss=5&css=&dt=1&specialTxt=3&specialJson=&usertype=2' page_text = requests.get(url,headers=headers).text tree = etree.HTML(page_text) ip_list = tree.xpath('//body//text()') for ip in ip_list: dic = {'https':ip} ips_pool.append(dic) from lxml import etree url = 'https://www.xicidaili.com/nn/%d' #通用的url模板(不可变) all_ips = [] for page in range(1,5): new_url = format(url%page) page_text = requests.get(new_url,headers=headers,proxies=random.choice(ips_pool)).text tree = etree.HTML(page_text) #在xpath表达式中不能够出现tbody标签 tr_list = tree.xpath('//*[@id="ip_list"]//tr')[1:] for tr in tr_list: ip = tr.xpath('./td[2]/text()')[0] port = tr.xpath('./td[3]/text()')[0] type_ip = tr.xpath('./td[6]/text()')[0] dic = { 'ip':ip, 'port':port, 'type':type_ip } all_ips.append(dic) print(len(all_ips))
需求:将https://xueqiu.com/中的新闻数据进行爬取 爬虫中处理cookie的操做 手动处理:将cookie写在headers中 自动处理:session对象。 获取session对象:requests.Session() 做用: session对象和requests对象均可以对指定的url进行请求发送。只不过使用session进行请求发送的过程当中若是产生了cookie则cookie会被自动存储在session对象中
url = 'https://xueqiu.com/v4/statuses/public_timeline_by_category.json?since_id=-1&max_id=20352188&count=15&category=-1' news_json = requests.get(url,headers=headers).json() news_json #基于cookie操做的修正 session = requests.Session() url = 'https://xueqiu.com/v4/statuses/public_timeline_by_category.json?since_id=-1&max_id=20352188&count=15&category=-1' #将cookie存储到session中,目的是将cookie获取存储到session中 session.get('https://xueqiu.com/',headers=headers) #保证该次请求时携带对应的cookie才能够请求成功 news_json = session.get(url,headers=headers).json() news_json
使用线上的打码平台进行自动的识别: - 云打码 - 超级鹰 : - 注册《用户中心》身份的帐户 - 登录 - 建立一个软件 - 下载示例代码《开发文档》
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() chaojiying = Chaojiying_Client('bobo328410948', 'bobo328410948', '899370') #用户中心>>软件ID 生成一个替换 96001 im = open('a.jpg', 'rb').read() #本地图片文件路径 来替换 a.jpg 有时WIN系统需要// print(chaojiying.PostPic(im,1004)['pic_str']) #验证码识别函数的封装 def transformCode(imgPath,imgType): chaojiying = Chaojiying_Client('bobo328410948', 'bobo328410948', '899370') im = open(imgPath, 'rb').read() return chaojiying.PostPic(im,imgType)['pic_str']
版本一 :
版本一的问题 :
请求须要有动态的参数
一般请状况下动态变化的请求参数都会被隐藏在前台页面源码中
from urllib import request #验证码的识别:将验证码下载到本地而后提交给打吗平台进行识别 main_url = 'https://so.gushiwen.org/user/login.aspx?from=http://so.gushiwen.org/user/collect.aspx' page_text = requests.get(main_url,headers=headers).text tree = etree.HTML(page_text) code_src = 'https://so.gushiwen.org'+tree.xpath('//*[@id="imgCode"]/@src')[0] request.urlretrieve(code_src,'./code.jpg') #识别验证码 code_text = transformCode('./code.jpg',1004) login_url = 'https://so.gushiwen.org/user/login.aspx?from=http%3a%2f%2fso.gushiwen.org%2fuser%2fcollect.aspx' data = { '__VIEWSTATE': '8/BKAQBaZHn7+GP+Kl2Gx43fFO1NI32RMyVae0RyrtFQue3IAhzQKvkml41cIT42Y//OcQccA8AqGYkvB+NFkU43uaHqU69Y0Z1WT3ZRrr4vR+CF7JlBG29POXM=', '__VIEWSTATEGENERATOR': 'C93BE1AE', 'from': 'http://so.gushiwen.org/user/collect.aspx', 'email': 'www.zhangbowudi@qq.com', 'pwd': 'bobo328410948', 'code': code_text, 'denglu': '登陆', } print(code_text) page_text = requests.post(login_url,headers=headers,data=data).text with open('./login.html','w',encoding='utf-8') as fp: fp.write(page_text)
版本二 :
版本二遇到的问题 :
没有携带cookie ,且这个网站的cookie在验证码的请求里
#验证码的识别:将验证码下载到本地而后提交给打吗平台进行识别 main_url = 'https://so.gushiwen.org/user/login.aspx?from=http://so.gushiwen.org/user/collect.aspx' page_text = requests.get(main_url,headers=headers).text tree = etree.HTML(page_text) code_src = 'https://so.gushiwen.org'+tree.xpath('//*[@id="imgCode"]/@src')[0] request.urlretrieve(code_src,'./code.jpg') #解析出动态变化的请求参数 __VIEWSTATE = tree.xpath('//*[@id="__VIEWSTATE"]/@value')[0] __VIEWSTATEGENERATOR = tree.xpath('//*[@id="__VIEWSTATEGENERATOR"]/@value')[0] #识别验证码 code_text = transformCode('./code.jpg',1004) login_url = 'https://so.gushiwen.org/user/login.aspx?from=http%3a%2f%2fso.gushiwen.org%2fuser%2fcollect.aspx' data = { '__VIEWSTATE': __VIEWSTATE, '__VIEWSTATEGENERATOR': __VIEWSTATEGENERATOR, 'from': 'http://so.gushiwen.org/user/collect.aspx', 'email': 'www.zhangbowudi@qq.com', 'pwd': 'bobo328410948', 'code': code_text, 'denglu': '登陆', } print(code_text) page_text = requests.post(login_url,headers=headers,data=data).text with open('./login.html','w',encoding='utf-8') as fp: fp.write(page_text)
版本三 (完美版):
s = requests.Session() #验证码的识别:将验证码下载到本地而后提交给打吗平台进行识别 main_url = 'https://so.gushiwen.org/user/login.aspx?from=http://so.gushiwen.org/user/collect.aspx' page_text = s.get(main_url,headers=headers).text tree = etree.HTML(page_text) code_src = 'https://so.gushiwen.org'+tree.xpath('//*[@id="imgCode"]/@src')[0] # request.urlretrieve(code_src,'./code.jpg') code_data = s.get(code_src,headers=headers).content with open('./code.jpg','wb') as fp: fp.write(code_data) #解析出动态变化的请求参数 __VIEWSTATE = tree.xpath('//*[@id="__VIEWSTATE"]/@value')[0] __VIEWSTATEGENERATOR = tree.xpath('//*[@id="__VIEWSTATEGENERATOR"]/@value')[0] #识别验证码 code_text = transformCode('./code.jpg',1004) login_url = 'https://so.gushiwen.org/user/login.aspx?from=http%3a%2f%2fso.gushiwen.org%2fuser%2fcollect.aspx' data = { '__VIEWSTATE': __VIEWSTATE, '__VIEWSTATEGENERATOR': __VIEWSTATEGENERATOR, 'from': 'http://so.gushiwen.org/user/collect.aspx', 'email': 'www.zhangbowudi@qq.com', 'pwd': 'bobo328410948', 'code': code_text, 'denglu': '登陆', } print(code_text) page_text = s.post(login_url,headers=headers,data=data).text with open('./login.html','w',encoding='utf-8') as fp: fp.write(page_text)
- 反爬机制 - robots - UA检测 - 图片懒加载 - 代理 - cookie - 验证码 - 动态变化的请求参数 - 动态加载的数据
# 同步操做 import time start = time.time() def request(url): print('正在请求',url) time.sleep(2) print('请求完毕:',url) urls = [ 'www.1.com', 'www.b.com', 'www.3.com' ] for url in urls: request(url) print('总耗时:',time.time()-start)
# 异步操做 import time from multiprocessing.dummy import Pool start = time.time() pool = Pool(3) def request(url): print('正在请求',url) time.sleep(2) print('请求完毕:',url) urls = [ 'www.1.com', 'www.b.com', 'www.3.com' ] pool.map(request,urls) print('总耗时:',time.time()-start)
# 爬虫+ 线程池 # server端 from flask import Flask,render_template from time import sleep app = Flask(__name__) @app.route('/bobo') def index_bobo(): sleep(2) return render_template('ip.html') @app.route('/jay') def index_jay(): sleep(2) return render_template('login.html') app.run() # 爬虫 + 线程池 import time from multiprocessing.dummy import Pool import requests from lxml import etree start = time.time() urls = [ 'http://localhost:5000/jay', 'http://localhost:5000/bobo' ] def get_request(url): page_text = requests.get(url).text return page_text def parse(page_text): tree = etree.HTML(page_text) print(tree.xpath('//div[1]//text()')) pool = Pool(2) page_text_list = pool.map(get_request,urls) pool.map(parse,page_text_list) print(len(page_text_list)) print('总耗时:',time.time()-start)
1. 特殊的函数: - 若是一个函数的定义被async修饰后,则该函数就是一个特殊的函数。 - 协程: - 对象。特殊函数被调用后,函数内部的实现语句不会被当即执行,而后该函数 调用会返回一个协程对象。 - 结论:协程对象==特殊的函数调用 2. 任务对象 - 实际上是对协程对象的进一步封装。 - 结论:任务对象==高级的协程对象==特殊的函数调用 - 绑定回调: - 回调函数何时被执行? - 任务对象执行结束后执行回调函数 - task.add_done_callback(func) - func必需要有一个参数,该参数表示的是该回调函数对应的任务对象 - 回调函数的参数.result():任务对象对应特殊函数内部的返回值 3. 事件循环对象 - 做用:将其内部注册的任务对象进行异步执行。 - 编码流程: - 定义特殊函数 - 建立协程对象 - 封装任务对象 - 建立事件循环对象 - 将任务对象注册到事件循环中且开启事件循环对象 - 注意:在特殊函数内部的实现语句中不能够出现不支持异步的模块对应的代码,不然 就是终止多任务异步协程的异步效果 - 注意重点:requests模块不支持异步,在多任务的异步协程中不可使用requests - aiohttp - 概念:支持异步的网络请求模块 - 编码流程: - 写基本架构: with aiohttp.ClientSession() as s: with s.get(url) as response: page_text = response.text() return page_text - 补充细节: - 添加async关键字 - 每个with前加上async - 添加await关键字 - 加载每一步的阻塞操做前加上await - 请求 - 获取响应数据
import asyncio from time import sleep #函数的定义 async修饰函数后成为特殊的函数 async def get_request(url): print('正在请求:',url) sleep(1) print('请求结束:',url) #函数调用:返回的就是一个协程对象 c = get_request('www.1.com') #建立3个协程对象 urls = [ '1.com','2.com','3.com' ] coroutine_list = [] # 协程对象列表 for url in urls: c = get_request(url) coroutine_list.append(c) print(coroutine_list) # 注意,此时协程对象并不能被执行
import asyncio from time import sleep #函数的定义 async def get_request(url): print('正在请求:',url) sleep(1) print('请求结束:',url) #函数调用:返回的就是一个协程对象 c = get_request('www.1.com') #建立一个任务对象:基于协程对象建立 task = asyncio.ensure_future(c) #建立3个协程对象 urls = [ '1.com','2.com','3.com' ] task_list = [] #存放多个任务对象的列表 for url in urls: c = get_request(url) task = asyncio.ensure_future(c) # 封装任务对象 task_list.append(task)
import asyncio from time import sleep #函数的定义 async def get_request(url): print('正在请求:',url) sleep(1) print('请求结束:',url) c = get_request('www.1.com') task = asyncio.ensure_future(c) #建立一个事件循环对象 loop = asyncio.get_event_loop() #将任务对象注册到事件循环对象中而且开启事件循环 loop.run_until_complete(task)
import asyncio from time import sleep import time #函数的定义 async def get_request(url): print('正在请求:',url) await asyncio.sleep(3) # time模块不支持异步,用asyncio代替 await为等待相关阻塞操做执行再执行 print('请求结束:',url) #建立3个协程对象 urls = [ '1.com','2.com','3.com' ] start = time.time() #任务列表:存储的是多个任务对象 tasks = [] for url in urls: c = get_request(url) task = asyncio.ensure_future(c) tasks.append(task) loop = asyncio.get_event_loop() loop.run_until_complete(asyncio.wait(tasks)) # wait为挂起 print('总耗时:',time.time()-start)
import asyncio from time import sleep import time #函数的定义 async def get_request(url): print('正在请求:',url) await asyncio.sleep(3) print('请求结束:',url) return 'bobo' def parse(task): # 回调函数必需要有一个参数,task为绑定的任务对象,task.result()返回的是特殊函数的返回值 print('i am task callback()!!!=----',task.result()) #建立3个协程对象 urls = [ '1.com','2.com','3.com' ] start = time.time() #任务列表:存储的是多个任务对象 tasks = [] for url in urls: c = get_request(url) task = asyncio.ensure_future(c) #绑定回调函数 task.add_done_callback(parse) tasks.append(task) loop = asyncio.get_event_loop() loop.run_until_complete(asyncio.wait(tasks)) print('总耗时:',time.time()-start)
爬取本身搭建的服务器
服务端 :
from flask import Flask,render_template from time import sleep app = Flask(__name__) @app.route('/ip') def index_1(): sleep(2) return render_template('ip.html') @app.route('/ip1') def index_2(): sleep(2) return render_template('ip.html') app.run(debug=True)
爬取
import asyncio import requests import time import aiohttp from lxml import etree #特殊函数:发起请求获取页面源码数据 # async def get_request(url): # #requests是一个不支持异步的模块 # page_text = requests.get(url).text # return page_text async def get_request(url): async with aiohttp.ClientSession() as s: #get/post:proxy = 'http://ip:port' #url,headers,data/prames跟requests一直 async with await s.get(url) as response: page_text = await response.text()#text()字符串形式的响应数据。read()二进制的响应数据 return page_text def parse(task): page_text = task.result() tree = etree.HTML(page_text) print(tree.xpath('//*[@id="10"]//text()')) urls = [ 'http://localhost:5000/ip1', 'http://localhost:5000/ip', 'http://localhost:5000/ip1', 'http://localhost:5000/ip', 'http://localhost:5000/ip1', 'http://localhost:5000/ip', ] start = time.time() tasks = [] #任务列表 for url in urls: c = get_request(url) task = asyncio.ensure_future(c) #绑定回调:用做于数据解析 task.add_done_callback(parse) tasks.append(task) loop = asyncio.get_event_loop() # 建立事物循环对象 loop.run_until_complete(asyncio.wait(tasks)) # 将任务对象注册到事件循环对象中而且开启事件循环 print('总耗时:',time.time()-start)
selenium : - 概念:基于浏览器自动化的一个模块。 - Appium是基于手机的自动化的模块。 - selenium和爬虫之间的关联 - 便捷的爬取到动态加载的数据 - 可见便可得 - 便捷的实现模拟登录 - 基本使用: - 环境安装 - pip install selenium - 下载浏览器的驱动程序 - http://chromedriver.storage.googleapis.com/index.html - 浏览器版本和驱动程序的映射关系: https://blog.csdn.net/huilan_same/article/details/51896672 - 动做链 - 在使用find系列的函数进行标签订位的时候若是出现了NoSuchElementException如何处理? - 若是定位的标签是存在于iframe标签之下的,则在进行指定标签订位的时候 必须使用switch_to.frame()的操做才可。 无头浏览器 : - phantomjs - 谷歌无头浏览器(推荐) 如何规避selenium被监测到的风险 - 网站能够根据:window.navigator.webdriver的返回值鉴定是否使用了selenium (浏览器开发者工具的console窗口下测试) - undefind:正常 - true:selenium 则检测到了是selenium访问的
from selenium import webdriver import time #实例化某一款浏览器对象 bro = webdriver.Chrome(executable_path='chromedriver.exe') #基于浏览器发起请求 bro.get('https://www.jd.com/') #商品搜索 #标签订位 search_input = bro.find_element_by_id('key') #往定位到的标签中录入数据 search_input.send_keys('袜子') #点击搜索按钮 btn = bro.find_element_by_xpath('//*[@id="search"]/div/div[2]/button') time.sleep(2) btn.click() time.sleep(2) #滚轮滑动(js注入) bro.execute_script('window.scrollTo(0,document.body.scrollHeight)') # time.sleep(6) bro.quit() # 退出浏览器
from selenium import webdriver import time from lxml import etree #实例化某一款浏览器对象 bro = webdriver.Chrome(executable_path='chromedriver.exe') bro.get('https://www.fjggfw.gov.cn/Website/JYXXNew.aspx') time.sleep(1) #page_source:当前页面全部的页面源码数据 page_text = bro.page_source #存储前3页对应的页面源码数据 all_page_text = [page_text] for i in range(3): next_page_btn = bro.find_element_by_xpath('//*[@id="kkpager"]/div[1]/span[1]/a[7]') next_page_btn.click() time.sleep(1) all_page_text.append(bro.page_source) for page_text in all_page_text: tree = etree.HTML(page_text) title = tree.xpath('//*[@id="list"]/div[1]/div/h4/a/text()')[0] print(title)
from selenium import webdriver from selenium.webdriver import ActionChains #动做连的类 from time import sleep bro = webdriver.Chrome(executable_path='chromedriver.exe') bro.get('https://www.runoob.com/try/try.php?filename=jqueryui-api-droppable') sleep(1) # - 在使用find系列的函数进行标签订位的时候若是出现了NoSuchElementException如何处理? # - 若是定位的标签是存在于iframe标签之下的,则在进行指定标签订位的时候必须使用switch_to.frame()的操做才可。 bro.switch_to.frame('iframeResult') #frame的参数为iframe标签的id属性值 div_tag = bro.find_element_by_id('draggable') #基于动做连实现滑动操做 action = ActionChains(bro) #点击且长按 action.click_and_hold(div_tag) for i in range(5): #perform()表示让动做连当即执行 action.move_by_offset(20,0).perform() sleep(0.5) sleep(3) bro.quit()
from selenium import webdriver from time import sleep from selenium.webdriver.chrome.options import Options # 建立一个参数对象,用来控制chrome以无界面模式打开 chrome_options = Options() chrome_options.add_argument('--headless') chrome_options.add_argument('--disable-gpu') bro = webdriver.Chrome(executable_path='chromedriver.exe',chrome_options=chrome_options) bro.get('https://www.taobao.com/') bro.save_screenshot('./123.png') # 截图保存 图片格式必须是png,不然报错 print(bro.page_source)
from selenium import webdriver from time import sleep from selenium.webdriver import ChromeOptions option = ChromeOptions() option.add_experimental_option('excludeSwitches', ['enable-automation']) # 后面是你的浏览器驱动位置,记得前面加r'','r'是防止字符转义的 bro = webdriver.Chrome(r'chromedriver.exe',options=option) bro.get('https://www.taobao.com/')
# 超级鹰示例代码 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()
from ChaoJiYing import Chaojiying_Client #导入超级鹰示例代码 from selenium import webdriver from selenium.webdriver import ActionChains from time import sleep #下载pil或者是Pillow from PIL import Image def transformCode(imgPath,imgType): # 超级鹰封装的函数 返回的是一组坐标 chaojiying = Chaojiying_Client('bobo328410948', 'bobo328410948', '899370') im = open(imgPath, 'rb').read() return chaojiying.PostPic(im,imgType)['pic_str'] bro = webdriver.Chrome(executable_path='chromedriver.exe') bro.get('https://kyfw.12306.cn/otn/login/init') sleep(2) bro.save_screenshot('main.png') #在main.jpg中截取下验证码图片 img_tag = bro.find_element_by_xpath('//*[@id="loginForm"]/div/ul[2]/li[4]/div/div/div[3]/img') location = img_tag.location # 图片左上角坐标 size = img_tag.size #img标签对应图片的长宽(尺寸) #裁剪范围 rangle = (location['x'],location['y'],location['x']+size['width'],location['y']+size['height']) # 裁剪12306验证码 i = Image.open('./main.png') frame = i.crop(rangle) frame.save('code.png') result = transformCode('./code.png',9004) # 识别后的坐标原点是基于识别图片而来的 # 坐标转换 260,140|260,139 ==> [[260,140],[260,139]] all_list = []#[[260,140],[260,139]] # 坐标列表 if '|' in result: list_1 = result.split('|') count_1 = len(list_1) for i in range(count_1): xy_list = [] x = int(list_1[i].split(',')[0]) y = int(list_1[i].split(',')[1]) xy_list.append(x) xy_list.append(y) all_list.append(xy_list) else: x = int(result.split(',')[0]) y = int(result.split(',')[1]) xy_list = [] xy_list.append(x) xy_list.append(y) all_list.append(xy_list) for xy in all_list: x = xy[0] y = xy[1] # 要先找到识别图片的标签img_tag,而后基于识别图片坐标进行点击事件 ActionChains(bro).move_to_element_with_offset(img_tag,x,y).click().perform() sleep(1)
1. 在页面中更换查找条件可让抓包工具捕获到咱们想要的数据包 2. apistudyapi.php该数据包就是咱们最终定位到的爬取数据对应的数据包 a. 该数据包中能够提取到url和请求参数(多是一组密文,而后该是动态变化) b. 响应数据的是通过加密的密文 3. 当修改了查询条件后且点击了查询按钮后发起了一个ajax请求,该请求就能够请求到apistudyapi.php数据包 a. 想要捕获的数据是能够经过点击搜索按钮生成的 4. 经过火狐浏览器的开发者工具能够找到搜索按钮绑定的点击事件对应的事件函数(getData()) 5. 分析getData():在该函数实现内部没有找到ajax请求对应的操做 a. type这个变量能够为HOUR b. getAQIData();getWeatherData(); 6. 分析:getAQIData();getWeatherData(); + 发现这两个函数的实现除了method变量的赋值不一样剩下的都一致 a. method = (GETDETAIL 或者 GETCITYWEATHER) b. 在这两个函数的实现中也没有发现ajax请求对应的代码,可是发现了一个叫作getServerData的函数调用,则分析a jax请求对应的代码确定是存在于getServerData这个函数的实现中 c. getServerData(method, param,匿名函数,0.5) -method = (GETDETAIL 或者 GETCITYWEATHER) -param是一个字典,内部有四组(city,type,startTime,endTime)键值对 7.分析getServerData函数的实现 : a. 最终经过抓包工具的全局搜索定位到了该函数的实现,可是实现的js代码被加密了,该种形式的加密被称为js混淆。 b.如何破解js混淆? - http://www.bm8.com.cn/jsConfusion/进行js反混淆 c. 在该函数的实现中终于找到了ajax请求对应的代码: - ajax请求的url - ajax请求方式 - 请求参数的来源:getParam(method, param) - 对加密的响应数据解密:decodeData(密文) 8.基于python模拟执行js代码 : - PyExecJS模块可让python模拟执行js代码 - 环境安装: - pip install PyExecJS - 在本机安装nodejs的开发环境
此过程是本地化的
#获取ajax请求的动态变化且加密的请求参数(d:xxx) import execjs node = execjs.get() # 实例化一个对象 # Params method = 'GETDETAIL' city = '北京' type = 'HOUR' start_time = '2018-01-25 00:00:00' end_time = '2018-01-25 23:00:00' # Compile javascript file = 'test.js' # 将ajax请求的js代码放在这里 ctx = node.compile(open(file,encoding='utf-8').read()) # 编译js代码 # Get params js = 'getPostParamCode("{0}", "{1}", "{2}", "{3}", "{4}")'.format(method, city, type, start_time, end_time) params = ctx.eval(js) #执行js代码 print(params) # 获取到的params是动态加密过的请求参数
#发起post请求 headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36' } url = 'https://www.aqistudy.cn/apinew/aqistudyapi.php' response_text = requests.post(url, data={'d': params},headers=headers).text print(response_text) # 获取到的响应数据是加密的
#对加密的响应数据进行解密 js = 'decodeData("{0}")'.format(response_text) decrypted_data = ctx.eval(js) print(decrypted_data)
#对捕获到的加密的响应数据进行解密 import execjs import requests node = execjs.get() # Params method = 'GETDETAIL' city = '北京' type = 'HOUR' start_time = '2018-01-25 00:00:00' end_time = '2018-01-25 23:00:00' # Compile javascript file = 'test.js' ctx = node.compile(open(file,encoding='utf-8').read()) # Get params js = 'getPostParamCode("{0}", "{1}", "{2}", "{3}", "{4}")'.format(method, city, type, start_time, end_time) params = ctx.eval(js) #执行js代码 #发起post请求 headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36' } url = 'https://www.aqistudy.cn/apinew/aqistudyapi.php' response_text = requests.post(url, data={'d': params},headers=headers).text #对加密的响应数据进行解密 js = 'decodeData("{0}")'.format(response_text) decrypted_data = ctx.eval(js) print(decrypted_data)
- scrapy框架 - 高性能的网络请求 - 高性能的数据解析 - 高性能的持久化存储 - 深度爬取 - 全栈爬取 - 分布式 - 中间件 - 请求传参
- 环境的安装: - mac/linux:pip install scrapy - window: - pip install wheel - 安装wheel工具 - 下载twisted http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted - 下载twisted架构 - 进入下载目录,执行 pip install Twisted‑17.1.0‑cp35‑cp36m‑win_amd64.whl -安装twisted 报错换版本 scrapy的异步是基于twisted实现的 - pip install pywin32 - pip install scrapy
- 基本使用 1. 新建一个工程 :pycharm终端中 :scrapy startproject ProName 目录结构: - spiders(包):空包 - settings:配置文件 - 不听从robots - UA假装 - 日志等级的指定 2. 命令行进入工程目录中 : cd ProName3 3. 在spiders(爬虫文件夹(包))中建立一个py爬虫文件 : scrapy genspider pachong www.xxx.com 该pachong文件建立在了spiders文件夹中 4. 编写代码 : 主要的爬虫代码写在了爬虫文件中 5. 执行工程 : 命令行中 : scrapy crawl pachong scrapy crawl pachong --nolog 不显示日志
# -*- coding: utf-8 -*- import scrapy # 爬虫类 父类是 Spider class PachongSpider(scrapy.Spider): name = 'pachong' # name 爬虫文件名 : 当前爬虫源文件的惟一标识 # 被容许的域名 allowed_domains = ['www.xxx.com'] # 起始的url,列表中存放的url均可以被scrapy进行异步网络请求 start_urls = ['http://www.xxx.com/'] # parse用做数据解析,response为响应对象 def parse(self, response): pass
1. 指定打印日等级 LOG_LEVEL = 'ERROR' 2. 不听从robots协议 ROBOTSTXT_OBEY = False 3. UA假装 USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36'
- scrapy的数据解析 - extract、extract_first()做用
- 终端中 : 1. scrapy startproject scrapy01 2. cd scrapy01 2. scrapy genspider pachong www.xx.com
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36' ROBOTSTXT_OBEY = False LOG_LEVEL = 'ERROR'
# -*- coding: utf-8 -*- import scrapy class PachongSpider(scrapy.Spider): name = 'pachong' # allowed_domains = ['www.xx.com'] start_urls = ['https://dig.chouti.com'] def parse(self, response): # 解析数据 div_list = response.xpath('/html/body/main/div/div/div[1]/div/div[2]/div[1]/div') for div in div_list: content = div.xpath('.//a[@class="link-title link-statistics"]/text()')[0].extract() content1 = div.xpath('.//a[@class="link-title link-statistics"]/text()').extract()[0] author = div.xpath('.//span[@class="left author-name"]/text()').extract_first() print(content,content1,author) # 测试 ,只取一个 break
终端 : scrapy crawl pachong
- 基于终端指令进行持久化存储 - 只能够将parse方法的返回值存储到本地的磁盘文件(指定形式后缀)中 - scrapy crawl spiderName -o filePath
爬虫代码
# -*- coding: utf-8 -*- import scrapy class PachongSpider(scrapy.Spider): name = 'pachong' # allowed_domains = ['www.xx.com'] start_urls = ['https://dig.chouti.com'] def parse(self, response): all_data = [] # 解析数据 div_list = response.xpath('/html/body/main/div/div/div[1]/div/div[2]/div[1]/div') for div in div_list: content = div.xpath('.//a[@class="link-title link-statistics"]/text()')[0].extract() content1 = div.xpath('.//a[@class="link-title link-statistics"]/text()').extract()[0] author = div.xpath('.//span[@class="left author-name"]/text()').extract_first() dic = { 'content':content, 'auther':author } all_data.append(dic) return all_data
持久化存储
只支持后缀 'json', 'jsonlines', 'jl', 'csv', 'xml', 'marshal', 'pickle' 终端命令 : scrapy crawl pachong -o data.csv
- 基于管道进行持久化存储(重点) - 编码流程 - 1.在爬虫文件中进行数据解析 - 2.在item类中定义相关的属性 - 3.将解析到的数据存储到一个item类型的对象中 - 4.将item类型的对象提交给管道 - 5.管道类的process_item方法负责接受item,接受到后能够对item实现任意形式的持久化存储操做 - 6.在配置文件中开启管道 - 一个管道类对应一种平台的持久化存储
爬虫代码 - 在爬虫文件中进行数据解析
# -*- coding: utf-8 -*- import scrapy # 导入Item的类,后期实例化须要 from scrapy002.items import Scrapy002Item class PachongSpider(scrapy.Spider): name = 'pachong' # allowed_domains = ['www.xx.com'] start_urls = ['https://dig.chouti.com'] def parse(self, response): all_data = [] # 解析数据 div_list = response.xpath('/html/body/main/div/div/div[1]/div/div[2]/div[1]/div') for div in div_list: # xpath在取标签的时候必需要用exetract()、exetract_first()进行字符串提取 content = div.xpath('.//a[@class="link-title link-statistics"]/text()')[0].extract() author = div.xpath('.//span[@class="left author-name"]/text()').extract_first() # 实例化一个item类型的对象 item = Scrapy002Item() # 给item对象的属性赋值 item['content'] = content item['author'] = author yield item # 将item提交给管道
items.py - 在item类中定义相关的属性
import scrapy class Scrapy002Item(scrapy.Item): # Field 类型是一个万能的数据类型 author = scrapy.Field() content = scrapy.Field()
pipelines.py - 将item类型的对象提交给管道 - 本地化存储
爬取的数据存储到本地 :
class Scrapy002Pipeline(object): # 重写父类,且该方法只会执行一次 def open_spider(self,spider): print('爬虫开始 ...') self.fp = open('pachong_ingf.txt','w',encoding='utf-8') # 该方法调用后就可接收爬虫类提交的item对象,且赋值给item参数 def process_item(self, item, spider): author = item['author'] content = item['content'] self.fp.write(author+':'+content+'\n') return item def close_spider(self,spider): print('爬虫结束!') self.fp.close()
爬取的数据存储到数据库中 :
1.在pipelines.py中写存储到数据库的类
class Scrapy002Pipeline(object): # 重写父类,且该方法只会执行一次 def open_spider(self,spider): print('爬虫开始 ...') self.fp = open('pachong_ingf.txt','w',encoding='utf-8') # 该方法调用后就可接收爬虫类提交的item对象,且赋值给item参数 def process_item(self, item, spider): author = item['author'] content = item['content'] self.fp.write(author+':'+content+'\n') return item # 此处是讲item传递给下一个即将执行的管道类 def close_spider(self,spider): print('爬虫结束!') self.fp.close() import pymysql class Mysql_data(object): conn = None cursor = None def open_spider(self, spider): self.conn = pymysql.Connection(host='127.0.0.1',port = 3306,user = 'root',password='2108',db = 'pachong',charset='utf8') def process_item(self, item, spider): author = item['author'] content = item['content'] sql = 'insert into pachong values ("%s","%s")'%(author,content) self.cursor = self.conn.cursor() #事物处理 : try : self.cursor.execute(sql) self.conn.commit() except Exception as e : print(e) self.conn.rollback() # 回滚事物 return item def close_spider(self,spider): self.cursor.close() self.conn.close()
在配置中注册相应的管道
ITEM_PIPELINES = { # 'scrapy002.pipelines.Scrapy002Pipeline': 300, 'scrapy002.pipelines.Mysql_data': 301, }
settings配置
开启管道,300为优先级,数值越小.优先级越高 ITEM_PIPELINES = { 'scrapy002.pipelines.Scrapy002Pipeline': 300, }
- scrapy的图片数据爬取(流数据的爬取) - scrapy中封装好了一个管道类(ImagesPipeline),基于该管道类能够实现图片资源的请求和持久化存储 - 编码流程: - 爬虫文件中解析出图片的地址 - 将图片地址封装到item中且提交给管道 - 管道文件中自定义一个管道类(父类:ImagesPipeline) - 重写三个方法: - def get_media_requests(self,item,info): - def file_path(self,request,response=None,info=None): - def item_completed(self,result,item,info): - 在配置文件中开启管道且加上IMAGES_STORE = './imgLibs'
写爬虫代码
import scrapy from imgage_pa.items import ImgagePaItem class ImgSpider(scrapy.Spider): name = 'img' # allowed_domains = ['www.xx.com'] start_urls = ['http://www.521609.com/xiaoyuanmeinv/'] def parse(self, response): li_list = response.xpath('//*[@id="content"]/div[2]/div[2]/ul/li') for li in li_list: item = ImgagePaItem() img_src = 'http://www.521609.com' + li.xpath('./a[1]/img/@src').extract_first() item['img_src'] = img_src yield item
items.py - 在item类中定义相关的属性
import scrapy class ImgagePaItem(scrapy.Item): img_src = scrapy.Field()
pipelines.py - 将item类型的对象提交给管道
from scrapy.pipelines.images import ImagesPipeline import scrapy class ImgagePaPipeline(ImagesPipeline): # 该方法是用于请求发送的 def get_media_requests(self, item, info): print(item) yield scrapy.Request(url=item['img_src']) # 指定图片路径 (文件夹 + 文件名) def file_path(self, request, response=None, info=None): return request.url.split('/')[-1] # 将item传递给下一个即将执行的管道类 def item_completed(self, results, item, info): return item
settings配置
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36' ROBOTSTXT_OBEY = False LOG_LEVEL = 'ERROR' IMAGES_STORE = './images' #指定爬取的图片存放的路径 ITEM_PIPELINES = { 'imgage_pa.pipelines.ImgagePaPipeline': 300, }
# 爬虫代码py文件中 # -*- coding: utf-8 -*- import scrapy from imgage_pa.items import ImgagePaItem class ImgSpider(scrapy.Spider): name = 'img' # allowed_domains = ['www.xx.com'] start_urls = ['http://www.521609.com/xiaoyuanmeinv/'] # 定义个通用的url模板 url = 'http://www.521609.com/xiaoyuanmeinv/list_%d.html' page_Num = 1 def parse(self, response): li_list = response.xpath('//*[@id="content"]/div[2]/div[2]/ul/li') for li in li_list: item = ImgagePaItem() img_src = 'http://www.521609.com' + li.xpath('./a[1]/img/@src').extract_first() item['img_src'] = img_src yield item if self.page_Num < 5: # 递归结束条件 - 爬取页码数 self.page_Num += 1 new_url = format((self.url % self.page_Num)) yield scrapy.Request(new_url, callback=self.parse) # 递归调用parse
- 在scrapy中如何进行手动请求发送 - yield scrapy.Request(url,callback) - 在scrapy中如何进行post请求的发送? - yield scrapy.FormRequest(url,callback,formdata) - 如何对起始的url进行post请求的发送? - 重写父类的start_requests(self): def start_requests(self): for url in self.start_urls: yield scrapy.FormRequest(url,callback=self.parse,formdata={}) # 调用parse函数
1. 增长并发: 默认scrapy开启的并发线程为32个,能够适当进行增长。在settings配置文件中修改CONCURRENT_REQUESTS = 100值为100,并发设置成了为100。 2. 下降日志级别: 在运行scrapy时,会有大量日志信息的输出,为了减小CPU的使用率。能够设置log输出信息为INFO或者ERROR便可。在配置文件中编写:LOG_LEVEL = ‘ERROR’ 3. 禁止cookie: 若是不是真的须要cookie,则在scrapy爬取数据时能够禁止cookie从而减小CPU的使用率,提高爬取效率。在配置文件中编写:COOKIES_ENABLED = False 4. 禁止重试: 对失败的HTTP进行从新请求(重试)会减慢爬取速度,所以能够禁止重试。在配置文件中编写:RETRY_ENABLED = False 5. 减小下载超时: 若是对一个很是慢的连接进行爬取,减小下载超时能够能让卡住的连接快速被放弃,从而提高效率。在配置文件中进行编写:DOWNLOAD_TIMEOUT = 1 超时时间为10s
- 请求传参(深度爬取) - 深度爬取: - 爬取的数据没有存在同一张页面中。 - 如何实现请求传参 - Request(url,callback,meta={}):能够将meta字典传递给callback - callback接收item:response.meta
爬虫代码
# -*- coding: utf-8 -*- import scrapy from movie.items import MovieItem class MoviePaSpider(scrapy.Spider): name = 'movie_pa' # allowed_domains = ['www.xx.com'] start_urls = ['https://www.4567tv.tv/index.php/vod/show/class/%E7%A7%91%E5%B9%BB/id/8.html '] # 通用URL模板 url = 'https://www.4567tv.tv/index.php/vod/show/class/科幻/id/8/page/%d.html' pageNum = 2 # 解析电影名称和详情页的url def parse(self, response): li_list = response.xpath('/html/body/div[1]/div/div/div/div[2]/ul/li') for li in li_list: title = li.xpath('./div/div/h4/a/text()').extract_first() detail_url ='https://www.4567tv.tv'+ li.xpath('./div/div/h4/a/@href').extract_first() # 实例化item item = MovieItem() item['title'] = title # 手动请求发送 yield scrapy.Request(detail_url,callback=self.parse_detail,meta={'item':item}) # 爬取多页 if self.pageNum < 5: new_url = format(self.url%self.pageNum) self.pageNum += 1 yield scrapy.Request(new_url,callback = self.parse) # 解析详情页中的电影简介 def parse_detail(self,response): item = response.meta['item'] detail_content = response.xpath('/html/body/div[1]/div/div/div/div[2]/p[5]/span[2]/text()').extract_first() item['detail_content'] = detail_content yield item
items
import scrapy class MovieItem(scrapy.Item): title = scrapy.Field() detail_content = scrapy.Field()
管道pipelines.py
class MoviePipeline(object): def process_item(self, item, spider): print(item) return item
settings配置
省略
1. 引擎(Scrapy) 用来处理整个系统的数据流处理, 触发事务(框架核心) 2. 调度器(Scheduler) 用来接受引擎发过来的请求, 压入队列中, 并在引擎再次请求的时候返回. 能够想像成一个URL(抓取网页的网址或者说是连接)的优先队列, 由它来决定下一个要抓取的网址是什么, 同时去除重复的网址,放在队列中 3. 下载器(Downloader) 用于下载网页内容, 并将网页内容返回给蜘蛛(Scrapy下载器是创建在twisted这个高效的异步模型上的) 4. 爬虫(Spiders) 爬虫是主要干活的, 用于从特定的网页中提取本身须要的信息, 即所谓的实体(Item)。用户也能够从中提取出连接,让Scrapy继续抓取下一个页面 5. 项目管道(Pipeline) 负责处理爬虫从网页中抽取的实体,主要的功能是持久化实体、验证明体的有效性、清除不须要的信息。当页面被爬虫解析后,将被发送到项目管道,并通过几个特定的次序处理数据。
1. 有哪些中间件。 - 下载中间件(推荐) - 爬虫中间件 2. 下载中间件的做用 - 批量拦截全部的请求和响应 3. 为何拦截请求 - 篡改请求的头信息(UA) - request.headers['User-Agent'] = 'xxxxx' - 代理 - request.meta['proxy'] = 'http://ip:port' 4. 为何拦截响应 - 篡改响应数据 - 篡改响应对象(推荐)
写爬虫代码
import scrapy class MiddleSpider(scrapy.Spider): name = 'middle___' # allowed_domains = ['www.xx.com'] start_urls = ['http://www.baidu.com/'] def parse(self, response): pass
写中间件
from scrapy import signals import random # UA池 user_agent_list = [ "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 " "(KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1", "Mozilla/5.0 (X11; CrOS i686 2268.111.0) AppleWebKit/536.11 " "(KHTML, like Gecko) Chrome/20.0.1132.57 Safari/536.11", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.6 " "(KHTML, like Gecko) Chrome/20.0.1092.0 Safari/536.6", "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.6 " "(KHTML, like Gecko) Chrome/20.0.1090.0 Safari/536.6", "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.1 " "(KHTML, like Gecko) Chrome/19.77.34.5 Safari/537.1", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/536.5 " "(KHTML, like Gecko) Chrome/19.0.1084.9 Safari/536.5", "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/536.5 " "(KHTML, like Gecko) Chrome/19.0.1084.36 Safari/536.5", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 " "(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3", "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/536.3 " "(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_0) AppleWebKit/536.3 " "(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3", "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 " "(KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 " "(KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3", "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 " "(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 " "(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3", "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/536.3 " "(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3", "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 " "(KHTML, like Gecko) Chrome/19.0.1061.0 Safari/536.3", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.24 " "(KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24", "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/535.24 " "(KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24" ] # 代理池 PROXY_http = [ 'http://153.180.102.104:80', 'http://195.208.131.189:56055', ] PROXY_https = [ 'https://120.83.49.90:9000', 'https://95.189.112.214:35508', ] class NewsWangyiDownloaderMiddleware(object): # 拦截请求与 def process_request(self, request, spider): # 能够将拦截到的请求尽量多的设置随机的UA request.headers['User-Agent'] = random.choice(user_agent_list) # 代理ip的设置 # if request.url.split(':')[0] == 'http': # request.meta['proxy'] = random.choice(PROXY_http) # else: # request.meta['proxy'] = random.choice(PROXY_https) return None # 拦截全部的响应 def process_response(self, request, response, spider): print('拦截响应') return response # 拦截异常的请求 def process_exception(self, request, exception, spider): print('异常拦截') # 将修正后的请求对象进行发送 return request # 将异常的请求从新发送 可是不会循环发送,发送几个后仍是异常就再也不进行发送
settings配置中开启中间件
DOWNLOADER_MIDDLEWARES = { 'news_wangyi.middlewares.NewsWangyiDownloaderMiddleware': 543, }
分析以下:
1.每一个板块下对应的新闻数据都是动态加载的 2.会对五个板块的响应数据进行数据解析,可是板块对应的响应对象是不包含动态加载的新闻数据,目前 获取的每个板块对应的响应对象是不知足需求的响应对象!!! 3.将不知足需求的5个响应对象(工程中一共会有1+5+n),修改为知足需求。 - 找到指定的5个不知足需求的响应对象(中间件) - 你的redis若是不能够写入字典 - pip install -U redis==2.10.6
建工程 : scrapy startproject wangyiNews
建爬虫文件 :
cd wangyiNews
scrapy genspider wangyipachong
import scrapy from selenium import webdriver from wangyiNews.items import WangyinewsItem class WangyiPaSpider(scrapy.Spider): name = 'wangyi_pa' # allowed_domains = ['www.xx.com'] start_urls = ['https://news.163.com/'] module_urls = [] # 五个板块所对应的url bro = webdriver.Chrome(executable_path=r'E:\Coding_Peasant\Project\爬虫\chromedriver.exe') # 解析出每个板块所对应的url def parse(self, response): li_list = response.xpath('//*[@id="index2016_wrap"]/div[1]/div[2]/div[2]/div[2]/div[2]/div/ul/li') # 获取 国内,国际,军事,航空,无人机板块的url indexs = [3, 4, 6, 7, 8] for index in indexs: li_tag = li_list[index] # 解析到每个板块对应的url module_url = li_tag.xpath('./a/@href').extract_first() # 将解析到的每个板块的url放到module_url属性中,传递给中间件 self.module_urls.append(module_url) # 对板块的url进行请求发送获取到每个板块对应的页面 # 手动发送请求 yield scrapy.Request(module_url, callback=self.parse_each_page) # 自定义解析每一个板块下对应的每个页面的新闻标题 def parse_each_page(self, response): div_list = response.xpath('/html/body/div/div[3]/div[4]/div[1]/div/div/ul/li/div/div') for div in div_list: item = WangyinewsItem() detail_url = div.xpath('./a/@href').extract_first() title = div.xpath('./div/div[1]/h3/a/text()').extract_first() if title and detail_url: item['title']=title # 交给parse_detail解析新闻详情 yield scrapy.Request(detail_url,callback=self.parse_detail,meta={'item':item}) # 解析新闻内容 def parse_detail(self,response): item = response.meta['item'] # //text() 返回的是列表,因此要用extract() content = response.xpath('//*[@id="endText"]//text()').extract() # 将新闻内容content列表转换成 content = ''.join(content) item['content'] = content yield item # 该方法在最后执行,关闭浏览器 def closed(self,spider): self.bro.quit()
from scrapy import signals from scrapy.http import HtmlResponse from time import sleep class WangyinewsDownloaderMiddleware(object): def process_request(self, request, spider): return None # 能够拦截到1+5+n个响应对象 def process_response(self, request, response, spider): # 根据5个板块的url定位到指定的request # 根据request定位到指定的response # 实例化selenium bro = spider.bro # spider是爬虫类实例化好的对象 module_urls = spider.module_urls if request.url in module_urls: # response就是最终五大板块对应的响应对象 bro.get(request.url) sleep(1) # 滚轮滚动 # bro.execute_script('window.scrollTo(0,document.body.scrollHeight)') # sleep(1) # bro.execute_script('window.scrollTo(0,document.body.scrollHeight)') # sleep(1) # bro.execute_script('window.scrollTo(0,document.body.scrollHeight)') # sleep(1) page_text = bro.page_source # url是响应请求所对应的url, body是获取到的响应数据 new_response = HtmlResponse(url=request.url,body=page_text,encoding='utf-8',request=request) return new_response else: return response def process_exception(self, request, exception, spider): pass
import scrapy class WangyinewsItem(scrapy.Item): title = scrapy.Field() content = scrapy.Field()
import pymysql from redis import Redis # 将爬取到的数据存储在sql中 class WangyinewsPipeline(object): conn = None cursor = None def open_spider(self,spider): self.conn = pymysql.Connection(host='127.0.0.1',port=3306,user='root',password='2108',db='pachongend',charset='utf8') print(self.conn) def process_item(self, item, spider): sql = 'insert into news values ("%s","%s")'%(item['title'],item['content']) self.cursor = self.conn.cursor() try: self.cursor.execute(sql) self.conn.commit() except Exception as e : print(e) self.conn.rollback() return item def close_spider(self,spider): self.cursor.close() self.conn.close() # 将爬取到的数据存储在redis中 class Wangyi_redis(object): conn = None def open_spider(self,spider): self.conn = Redis(host='127.0.0.1',port=6379) print(self.conn) def process_item(self,item,spider): self.conn.lpush('news',item['title']) self.conn.lpush('content',item['content'])
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36' ROBOTSTXT_OBEY = False LOG_LEVEL = 'ERROR' # 开启管道 ITEM_PIPELINES = { # 'wangyiNews.pipelines.WangyinewsPipeline': 300, 'wangyiNews.pipelines.Wangyi_redis': 300, }
http://ai.baidu.com/
http://ai.baidu.com/ 天然语言处理 → 建立应用 → 进入文档 → 天然语言处理 → 语言处理基础技术 → SDK文档 → Python语言
pip install baidu-aip
示例代码 :
""" 你的 APPID AK SK """ APP_ID = '17533126' API_KEY = '7W0ed4MYLHzlfke8ZoM1FwRr' SECRET_KEY = '0Gq28MBrwV4Yipyt07cxC9ON0xRichRb' client = AipNlp(APP_ID, API_KEY, SECRET_KEY) title = "欧洲冠军杯足球赛" content = "欧洲冠军联赛是欧洲足球协会联盟主办的年度足球比赛,表明欧洲俱乐部足球最高荣誉和水平,被认为是全世界最高素质、最具影响力以及最高水平的俱乐部赛事,亦是世界上奖金最高的足球赛事和体育赛事之一。" """ 调用文章标签 """ dic = client.topic(title, content) print(dic)
文章标签和文章分类处理 :
将百度的aip做用到管道上便可
import pymysql from redis import Redis from aip import AipNlp # 将爬取到的数据存储在sql中 class WangyinewsPipeline(object): conn = None cursor = None def open_spider(self,spider): self.conn = pymysql.Connection(host='127.0.0.1',port=3306,user='root',password='2108',db='pachongend',charset='utf8') print(self.conn) def process_item(self, item, spider): sql = 'insert into news values ("%s","%s")'%(item['title'],item['content']) self.cursor = self.conn.cursor() try: self.cursor.execute(sql) self.conn.commit() except Exception as e : print(e) self.conn.rollback() return item def close_spider(self,spider): self.cursor.close() self.conn.close() # 将爬取到的数据存储在redis中 class Wangyi_redis(object): conn = None client = None def open_spider(self,spider): """ 你的 APPID AK SK """ APP_ID = '17533126' API_KEY = '7W0ed4MYLHzlfke8ZoM1FwRr' SECRET_KEY = '0Gq28MBrwV4Yipyt07cxC9ON0xRichRb' self.client = AipNlp(APP_ID, API_KEY, SECRET_KEY) def process_item(self,item,spider): title = item['title'] content = item['content'] content = content.replace(u'\xa0',u'') # 解决gbk对'\xa0'编码的问题 n_tag = self.client.keyword(title, content)['items'][0]['tag'] n_type = self.client.topic(title, content)['item']['lv2_tag_list'][0]['tag'] print(n_tag,n_type)
CrawlSpider实现的全栈数据的爬取 1. 新建工程 srcapy startproject ProName 2. cd 工程 3. 建立爬虫文件:scrapy genspider -t crawl spiderName www.xxx.com 配置settings 4. 链接提取器LinkExtractor 能够根据指定的规则对指定的链接进行提取【提取的规则就是构造方法中的allow(‘正则表达式’)参数决定】 5. 规则解析器Rule 能够将将链接提取器提取到的链接进行请求发送,能够根据指定的规则(callback)对请求到的数据进行解析 6. follow=True 将链接提取器 继续做用到 链接提取器提取到的链接 所对应的 页面源码中 7. 执行爬虫命令 : scrapy ceawl py文件名
请求发送的三种方式: 1. 起始url 2. scrapy.Request() 3. 连接提取器发送请求提取器
settings配置
写爬虫代码
# -*- coding: utf-8 -*- import scrapy from scrapy.linkextractors import LinkExtractor from scrapy.spiders import CrawlSpider, Rule class SunSpider(CrawlSpider): name = 'sun' # allowed_domains = ['www.xx.com'] # 起始url不作解析 start_urls = ['http://wz.sun0769.com/index.php/question/questionType?type=4&page='] # link 连接提取器:根据指定的规则对指定的连接进行提取连接 # 提取的规则就是构造方法中的allow('正则表达式')参数决定 # LinkExtractor能够对页面的全部url进行提取allow=r''表示的页面的全部url, link = LinkExtractor(allow=r'type=4&page=\d+') rules = ( # 实例化一个rule对象(规则解析对象) # 对提取到的连接自动发送请求,根据指定的规则callback='parse_item'对请求的对象解析 # follow=True 表示的是将提取到的连接继续做用到 连接提取器上再次进行新提取的连接页面的连接提取 Rule(link, callback='parse_item', follow=False ), ) # 数据解析:解析提取连接请求所返回的响应 def parse_item(self, response): tr_list = response.xpath('//*[@id="morelist"]/div/table[2]//tr/td/table//tr') for tr in tr_list: title = tr.xpath('./td[2]/a[2]/text()').extract_first() status = tr.xpath('./td[3]/span/text()').extract_first() print(title,status)
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36' ROBOTSTXT_OBEY = False LOG_LEVEL = 'ERROR' # 开启管道 ITEM_PIPELINES = { 'sun_pa.pipelines.SunPaPipeline': 300, }
import scrapy from scrapy.linkextractors import LinkExtractor from scrapy.spiders import CrawlSpider, Rule from sun_pa.items import SunPaItem1,SunPaItem2 class SunSpider(CrawlSpider): name = 'sun' # allowed_domains = ['www.xx.com'] # 起始url不作解析 start_urls = ['http://wz.sun0769.com/index.php/question/questionType?type=4&page='] # link 连接提取器:根据指定的规则对指定的连接进行提取连接 # 提取的规则就是构造方法中的allow('正则表达式')参数决定 # LinkExtractor能够对页面的全部url进行提取allow=r''表示的页面的全部url, link = LinkExtractor(allow=r'type=4&page=\d+') link1 = LinkExtractor(allow=r'type=4&page=$') # 第一页 link_detail = LinkExtractor(allow=r'/question/\d+/\d+\.shtml') # \.转义点 rules = ( # 实例化一个rule对象(规则解析对象) # 对提取到的连接自动发送请求,根据指定的规则callback='parse_item'对请求的对象解析 # follow=True 表示的是将提取到的连接继续做用到 连接提取器上再次进行新提取的连接页面的连接提取 Rule(link, callback='parse_item', follow=False ), Rule(link1, callback='parse_item', follow=False ), Rule(link_detail, callback='parse_detail', follow=False ), ) # 数据解析:解析提取连接请求所返回的响应 def parse_item(self, response): tr_list = response.xpath('//*[@id="morelist"]/div/table[2]//tr/td/table//tr') for tr in tr_list: title = tr.xpath('./td[2]/a[2]/text()').extract_first() status = tr.xpath('./td[3]/span/text()').extract_first() num = tr.xpath('./td[1]/text()').extract_first() item = SunPaItem2() item['title']=title item['status']=status item['num']=num yield item # 解析详情页中的新闻内容 def parse_detail(self, response): content = response.xpath('/html/body/div[9]/table[2]//tr[1]/td//text()').extract() content = ''.join(content) num = response.xpath('/html/body/div[9]/table[1]//tr/td[2]/span[2]/text()').extract_first() num = num.split(':')[-1] item = SunPaItem1() item['content']= content item['num']= num yield item
import scrapy class SunPaItem1(scrapy.Item): content = scrapy.Field() num = scrapy.Field() class SunPaItem2(scrapy.Item): title = scrapy.Field() status = scrapy.Field() num = scrapy.Field()
class SunPaPipeline(object): def process_item(self, item, spider): if item.__class__.__name__ == 'SunPaItem1': content = item['content'] num = item['num'] print(num,content) else: title = item['title'] status = item['status'] num = item['num'] print(num,status,title) # 最后根据相应的num进行持续化存储 return item
这个网站的前5页和最后页的标签格式不一样,因此上述的基于CrawlSpider的深度爬取等我基于正则匹配url不能解析到最后页的url,爬取不到数据,下面更改用手动请求进行爬取
下面只附上爬虫代码
import scrapy from scrapy.linkextractors import LinkExtractor from scrapy.spiders import CrawlSpider, Rule from sun_pa.items import SunPaItem2 class SunSpider(CrawlSpider): name = 'sun' # allowed_domains = ['www.xx.com'] # 起始url不作解析 start_urls = ['http://wz.sun0769.com/index.php/question/questionType?type=4&page='] # link 连接提取器:根据指定的规则对指定的连接进行提取连接 # 提取的规则就是构造方法中的allow('正则表达式')参数决定 # LinkExtractor能够对页面的全部url进行提取allow=r''表示的页面的全部url, link = LinkExtractor(allow=r'type=4&page=\d+') link1 = LinkExtractor(allow=r'type=4&page=$') # 第一页 rules = ( # 实例化一个rule对象(规则解析对象) # 对提取到的连接自动发送请求,根据指定的规则callback='parse_item'对请求的对象解析 # follow=True 表示的是将提取到的连接继续做用到 连接提取器上再次进行新提取的连接页面的连接提取 Rule(link, callback='parse_item', follow=False ), Rule(link1, callback='parse_item', follow=False ), ) # 数据解析:解析提取连接请求所返回的响应 def parse_item(self, response): tr_list = response.xpath('//*[@id="morelist"]/div/table[2]//tr/td/table//tr') for tr in tr_list: title = tr.xpath('./td[2]/a[2]/text()').extract_first() status = tr.xpath('./td[3]/span/text()').extract_first() detail_url = tr.xpath('./td[2]/a[2]/@href').extract_first() item = SunPaItem2() item['title']=title item['status']=status yield scrapy.Request(detail_url,callback=self.parse_detail,meta={'item':item}) # 解析详情页中的新闻内容 def parse_detail(self, response): item = response.meta['item'] content = response.xpath('/html/body/div[9]/table[2]//tr[1]//text()').extract() content = ''.join(content) item['content'] = content yield item
1. 实现方式:scrapy+scrapy_redis组件实现的分布式。scrapy+redis 2. 原生的scrapy是不能够实现分布式的!!! 3. 什么是分布式 - 须要搭建一个由n台电脑组成的机群,而后在每一台电脑中执行同一组程序,让其对同一个网络资源 进行联合且分布的数据爬取。 4. 为何scrapy不能够实现分布式 - 调度器不能够被共享 - 管道不能够被共享 5. scrapy-reids组件的做用是什么 - 提供能够被共享的管道和调度器
分布式的实现流程 1. 环境的安装 : pip install scrapy-redis 2. 建立工程 : scrapy startproject ProName 3. cd 工程 4. 爬虫文件的建立的两种方式 1. 基于Spider : scrapy genspider spiderName 2. 基于CrawSpider : scrapy genspider -t crawl spiderName www.xxx.com 5. 修改爬虫文件 1. 导包 : 1. 基于CrawlSpider爬虫 : from scrapy_redis.spiders import RedisCrawlSpider 2. 基于Spider爬虫文件 : from scrapy_redis.spiders import RedisSpider 2. 将当前爬虫类的父类修改成 RedisCrawlSpider 3. 删除allowed_domains 和 start_urls 4. 添加一个redis_key = 'xx'的属性,表示的是调度器队列的名称 5. 根据常规形式编写爬虫代码 6. 修改settings配置文件 1. 指定管道 ITEM_PIPELINES = { 'scrapy_redis.pipelines.RedisPipeline': 400 } 2. 指定调度器 1. 增长了一个去重容器类的配置, 做用使用Redis的set集合来存储请求的指纹数据, 从而实现请求去重 的持久化 DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter" 2. 使用scrapy-redis组件本身的调度器 SCHEDULER = "scrapy_redis.scheduler.Scheduler" 3. 配置调度器是否要持久化, 也就是当爬虫结束了, 要不要清空Redis中请求队列和去重指纹的set。如 果是True, 就表示要持久化存储, 就不清空数据, 不然清空数据 SCHEDULER_PERSIST = True 3. 指定redis REDIS_HOST = '192.168.13.254' #须要存储redis数据的电脑的ip REDIS_PORT = 6379 7. 修改redis的配置文件(redis.windows.conf) 1. 关闭默认绑定 注释掉 56行中的 : bind 127.0.0.1 # 注释掉后任何人均可以访问redis数据库 2. 关闭保护模式 75行 : protected-mode no # 不关闭的话,其余电脑访问只能读数据,不能写数据 8. 启动redis的服务端(携带配置文件)和客户端 启动服务端的代码 :redis-server.exe redis.windows.conf 9. 启动分布式的爬虫程序 1. cd 爬虫文件所对应的目录中 2. 执行命令 : scrapy runspider 爬虫文件.py 执行后服务就开始监听 3. 向调度器的队列放一个起始url (队列是存在于客户端的redis中的) : redis客户端 : lpush sun www.xx.com (www.xx.com是起始url) 4. redis中就能够查看爬取 的数据了 5. redis的requests是已经爬取过的url列表 10. 实现机群分布式爬取 1. 修改redis的配置文件(redis.windows.conf) 2. 启动redis的服务端(携带配置文件)和客户端 3. cd 爬虫文件所对应的目录中 执行命令 : scrapy runspider 爬虫文件.py 等待放入起始url
爬虫代码:
import scrapy from scrapy.linkextractors import LinkExtractor from scrapy.spiders import CrawlSpider, Rule from fbsPro.items import FbsproItem from scrapy_redis.spiders import RedisCrawlSpider from scrapy_redis.spiders import RedisSpider class FbsSpider(RedisCrawlSpider): name = 'fbs' # allowed_domains = ['www.xxx.com'] # start_urls = ['http://www.xxx.com/'] redis_key = 'sun' #可被共享的调度器队列的名称 rules = ( Rule(LinkExtractor(allow=r'type=4&page=\d+'), callback='parse_item', follow=True), ) def parse_item(self, response): tr_list = response.xpath('//*[@id="morelist"]/div/table[2]//tr/td/table//tr') for tr in tr_list: title = tr.xpath('./td[2]/a[2]/text()').extract_first() status = tr.xpath('./td[3]/span/text()').extract_first() item = FbsproItem() item['title'] = title item['status'] = status yield item
items
import scrapy class FbsproItem(scrapy.Item): # define the fields for your item here like: title = scrapy.Field() status = scrapy.Field()
settings配置
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36' ROBOTSTXT_OBEY = False LOG_LEVEL = 'ERROR' #指定管道 ITEM_PIPELINES = { 'scrapy_redis.pipelines.RedisPipeline': 400 } #指定调度器 # 增长了一个去重容器类的配置, 做用使用Redis的set集合来存储请求的指纹数据, 从而实现请求去重的持久化 DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter" # 使用scrapy-redis组件本身的调度器 SCHEDULER = "scrapy_redis.scheduler.Scheduler" # 配置调度器是否要持久化, 也就是当爬虫结束了, 要不要清空Redis中请求队列和去重指纹的set。若是是True, 就表示要持久化存储, 就不清空数据, 不然清空数据 SCHEDULER_PERSIST = True #指定redis REDIS_HOST = '192.168.13.254' REDIS_PORT = 6379
基于Celery的分布式/Pypperteer(异步的scrapy)
python芹菜 - Celery
增量式 - 概念:监测 - 核心技术:去重 - 适合使用增量式的网站: - 基于深度爬取 - 对爬取过的页面的url进行一个记录(记录表) - 基于非深度爬取 - 记录表:爬取过的数据对应的数据指纹 - 数据指纹:就是原始数据的一组惟一标识 - 所谓的记录表是以怎样的形式存在于哪? - redis的set充当记录表
scrapy startproject pachong
→ cd pachong
→ scrapy genspider -t crawl pachong_py www.xx.com
# -*- coding: utf-8 -*- import scrapy from scrapy.linkextractors import LinkExtractor from scrapy.spiders import CrawlSpider, Rule from redis import Redis # 导入redis模块 from pachong.items import PachongItem class PachongPySpider(CrawlSpider): name = 'pachong_py' # allowed_domains = ['www.xx.com'] start_urls = ['https://www.4567tv.tv/index.php/vod/show/class/%E5%8A%A8%E4%BD%9C/id/1.html'] conn = Redis(host='127.0.0.1',port=6379) # 实例化redis管道 rules = ( Rule(LinkExtractor(allow=r'page/\d+\.html'), callback='parse_item', follow=True), ) def parse_item(self, response): li_list = response.xpath('/html/body/div[1]/div/div/div/div[2]/ul/li') for li in li_list: movie_name = li.xpath('./div/div/h4/a/text()').extract_first() detail_url = 'https://www.4567tv.tv'+ li.xpath('./div/div/h4/a/@href').extract_first() # 向redis插入数据,movie_url为自定义的数据字段 ex = self.conn.sadd('movie_url',detail_url) # redis插入数据有返回值,若返回值是1,说明redis的集合set中没有此条新数据,若为0,说明有此条数据,不能插入 if ex == 1: # 返回值为1 ,则说明没有爬取过,而后执行回调爬取 print('有更新,正在爬取...') item = PachongItem() item['title'] = movie_name yield scrapy.Request(detail_url,callback=self.parse_detail,meta={'item':item}) else: # 爬取过,不须要再次爬取 print('没有更新的电影数据!!') def parse_detail(self,response): item = response.meta['item'] content = response.xpath('/html/body/div[1]/div/div/div/div[2]/p[5]/span[2]/text()').extract_first() item['content'] = content yield item
import scrapy class PachongItem(scrapy.Item): title = scrapy.Field() content = scrapy.Field()
class PachongPipeline(object): def process_item(self, item, spider): conn = spider.conn conn.lpush('movie_data',item) return item
- 反爬机制 - robots - UA假装 - 图片懒加载 - 验证码 - cookie - 动态加载的数据 - 动态变化的请求参数 - js加密 - js混淆 - 代理