爬虫

day01

  • 什么是anacanda
    • 基于数据分析+ML(机器学习)的一个集成环境
  • 什么是jupyter
    • 就是anacanda提供的一个编辑环境(基于浏览器)
  • cell有两种模式
    • code
    • markdown

快捷键

  • 插入cell:a,b

  • 删除cell:x

  • 执行cell:shift+enter

  • 切换cell的模式:m,y

  • tab:自动补全

  • shift+tab:打开帮助文档

  • 学习方法

    • 总结结论
      • user-agent:请求载体的身份标识
    • 重复

爬虫

  • requeests

  • 数据解析

  • 如何提升爬取数据的效率

    • 线程池
    • 多任务的异步携程
  • scrapy

    • 分布式
  • 基于移动端数据的爬取

  • selenium

数据分析:

  • 将数据的价值最大化

ML

  • 特征工程
  • 机器学习的基础
  • 回归模型
  • 分类模型
    • 集成学习

算法+数据结构

  • stack

  • 队列

  • 链表

  • 二叉树

  • 二分查找

  • 选择,冒泡

  • 插入

  • 希尔

  • 快速

  • 什么是爬虫

    • 通过编写程序模拟浏览器发请求,让其去互联网中爬取数据的过程。
  • 爬虫分类

    • 通用爬虫:抓取一整张页面源码的数据
    • 聚焦爬虫:抓取一张页面中局部的数据
      • 通用爬虫和聚焦爬虫之间的关联:
        • 聚焦需要建立在通用爬虫的基础之上
    • 增量式:监测网站数据更新的情况。以便将最新更新出来的数据进行爬取。
  • 爬虫的合法性探究

    • 爬虫所带来风险主要体现在以下2个方面:

      • 爬虫干扰了被访问网站的正常运营;
      • 爬虫抓取了受到法律保护的特定类型的数据或信息。
    • 那么作为爬虫开发者,如何在使用爬虫时避免进局子的厄运呢?

      • 严格遵守网站设置的robots协议;

      • 在规避反爬虫措施的同时,需要优化自己的代码,避免干扰被访问网站的正常运行;

      • 在使用、传播抓取到的信息时,应审查所抓取的内容,如发现属于用户的个人信息、隐私或者他人的商业秘密的,应及时停止并删除。

  • 反爬机制

    • robots协议:纯文本的协议。
      • User-Agent:请求载体的身份标识
      • 不遵从
  • 反反爬策略

requests基本介绍

  • 作用:实现爬虫。模拟浏览器上网。

  • 编码流程:

    • 指定url
    • 发起请求
    • 获取响应数据
    • 持久化存储
  • 环境的安装:pip install reqeusts

  • 将搜狗首页的页面源码数据进行爬取

import requests
#1.指定url
url = 'https://www.sogou.com/'
#2,发起请求
#get方法返回的是一个响应对象
response = requests.get(url=url)#根据指定的url发起get请求
#3.获取响应数据
page_text = response.text #test返回的是字符串形式的响应数据
#4.持久化存储
with open('./sogou.html','w',encoding='utf-8') as fp:
    fp.write(page_text)
  • 简易的网页采集器
#请求参数动态化
wd = input('enter a key word:')
params = {
    #url携带的请求参数
    'query':wd
}
url = 'https://www.sogou.com/web'
#params参数:实现参数动态化
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,'下载成功!!!')
  • 上述程序出问题:
    • 出现了乱码
    • 数据量级不吻合
#处理乱码问题
wd = input('enter a key word:')
params = {
    #url携带的请求参数
    'query':wd
}
url = 'https://www.sogou.com/web'
#params参数:实现参数动态化
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检测
  • 反反爬策略:UA伪装
#UA检测
wd = input('enter a key word:')
params = {
    #url携带的请求参数
    'query':wd
}
headers = {
    #需要修改的请求头信息
    'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36'
}
url = 'https://www.sogou.com/web'
#params参数:实现参数动态化
response = requests.get(url=url,params=params,headers=headers)#UA伪装进行的请求发送
#手动修改响应数据的编码格式
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,'下载成功!!!')
enter a key word:tom
tom.html 下载成功!!!
  • 需求:爬取豆瓣电影中爱情类型中的电影详情数据
    • 动态加载的数据:通过另一个请求单独请求到的数据
    • 如何检测爬取的数据是否为动态加载的数据?
      • 基于抓包工具进行局部搜索
        • 抓包工具捕获到所有的数据包,然后找到浏览器地址栏url对应的数据包,response这个选项卡中进行局部搜索(搜到,搜不到)。
          • 搜不到:数据为动态加载
            • 基于抓包工具进行全局搜索
          • 搜到:数据不是动态加载
            • 直接对浏览器地址栏的url发起请求获取数据即可
import requests
#UA检测会被应用在绝大数的网站中
headers = {
    #需要修改的请求头信息
    'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36'
}
params = {
    "type": "13",
    "interval_id": "100:90",
    "action": "",
    "start": "10",
    "limit": "200",
}
url = 'https://movie.douban.com/j/chart/top_list'
response = requests.get(url=url,headers=headers,params=params)
data_list = response.json()
for dic in data_list:
    name = dic['title']
    score = dic['score']
    print(name,score)
#捕获到了第一页对应的位置信息
url = 'http://www.kfc.com.cn/kfccda/ashx/GetStoreList.ashx?op=keyword'
city = input('enter a city name:')
data = {
    "cname": '',
    "pid": '',
    "keyword": city,
    "pageIndex": "1",
    "pageSize": "10",
}
pos_list = requests.post(url=url,headers=headers,data=data).json()['Table1']#data参数是用来实现参数动态化
for dic in pos_list:
    pos = dic['addressDetail']
    print(pos)

#捕获所有页码对应的位置信息
url = 'http://www.kfc.com.cn/kfccda/ashx/GetStoreList.ashx?op=keyword'
city = input('enter a city name:')
#全站数据的爬取
for pageNum in range(1,9):
    data = {
        "cname": '',
        "pid": '',
        "keyword": city,
        "pageIndex": str(pageNum),
        "pageSize": "10",
    }
    pos_list = requests.post(url=url,headers=headers,data=data).json()['Table1']#data参数是用来实现参数动态化
    for dic in pos_list:
        pos = dic['addressDetail']
        print(pos)

  • 需求:所有企业的企业详情数据

  • 分析:

    • 1.每一家企业对应的详情数据是动态加载出来的
    • 2.通过抓包工具进行了全局搜索,定位到了动态加载数据对应的数据包
      • 提取出url:每一家企业对应的url都一样
      • 请求方式:都一样
      • 请求参数:都为id但是参数值不同
        • id就是每一家企业的唯一标识
        • 如果可以将每一家企业的唯一标识id值捕获到,最终需求就可以完成
    • 对企业id值的捕获
      • 在首页中进行分析,使用抓包工具进行了企业名称的全局搜索,定位到的数据包的数据中就包含了企业名称和对应的id值
url = 'http://125.35.6.84:81/xk/itownet/portalAction.do?method=getXkzsById'
data = {
    'id':'3718324df7b94fb782f7e9bb5d185552'
}
requests.post(url=url,headers=headers,data=data).json()

#捕获企业id
url = 'http://125.35.6.84:81/xk/itownet/portalAction.do?method=getXkzsList'
ids = []
for pageNum in range(1,6):
    data = {
        "on": 'true',
        'page': str(pageNum),
        'pageSize': '15',
        'productName': '',
        'conditionType': '1',
        'applyname': '',
        'applysn': '',
    }
    company_list = requests.post(url=url,headers=headers,data=data).json()['list']
    for dic in company_list:
        _id = dic['ID']
        ids.append(_id)


#完整实现
#1.捕获到所有企业的id
url = 'http://125.35.6.84:81/xk/itownet/portalAction.do?method=getXkzsList'
ids = []
for pageNum in range(1,6):
    data = {
        "on": 'true',
        'page': str(pageNum),
        'pageSize': '15',
        'productName': '',
        'conditionType': '1',
        'applyname': '',
        'applysn': '',
    }
    company_list = requests.post(url=url,headers=headers,data=data).json()['list']
    for dic in company_list:
        _id = dic['ID']
        ids.append(_id)

#2.基于存储id的列表ids进行循环的请求发送
detail_url = 'http://125.35.6.84:81/xk/itownet/portalAction.do?method=getXkzsById'
for _id in ids:
    data = {
        'id':_id
    }
    company_data = requests.post(url=detail_url,headers=headers,data=data).json()
    legalPerson = company_data['legalPerson']
    address = company_data['epsProductAddress']
    print(legalPerson,address)

  • 总结
    • requests的基本使用
      • 编码流程
      • 搜狗首页数据爬取==》流程
      • 参数的动态化
      • UA伪装
      • 修改响应数据的编码格式
      • get/post:
        • url
        • headers
        • data/params
      • 动态加载的数据
      • 全站数据的爬取

day02

回顾

  • 问题:
    • ip被封:代理
    • 请求参数问题:
      • 动态变化的请求参数
      • 加密的请求参数
    • 响应数据的问题:
      • cookie
      • 请求参数
    • 加密:
      • js逆向
  • 重点内容
    • 参数的动态化
      • data/prames
    • 反爬机制:
      • robots.txt
      • UA检测
      • 动态加载的数据
        • 如何检测数据是否为动态加载
        • 如何捕获动态加载的数据
      • 动态加载的数据是如何生成?
        • ajax
        • js

数据解析

  • 作用:实现聚焦爬虫
  • 实现方式:
    • 正则
    • bs4:重点
    • xpath:重点
    • pyquery:自学
  • 数据解析的通用原理是什么?
    • 解析的一定是html页面的源码数据
      • 标签中存储的文本内容
      • 标签属性的属性值
    • 原理:
      • 标签定位
      • 取文本或者取属性
  • 爬虫实现的流程
    • 指定url
    • 发请求
    • 获取响应数据
    • 数据解析
    • 持久化存储
import requests
import re
headers = {
    #需要修改的请求头信息
    'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36'
}

正则解析

 单字符:
        . : 除换行以外所有字符
        [] :[aoe] [a-w] 匹配集合中任意一个字符
        \d :数字  [0-9]
        \D : 非数字
        \w :数字、字母、下划线、中文
        \W : 非\w
        \s :所有的空白字符包,括空格、制表符、换页符等等。等价于 [ \f\n\r\t\v]。
        \S : 非空白
    数量修饰:
        * : 任意多次  >=0
        + : 至少1次   >=1
                ? : 可有可无  0次或者1次
                {m} :固定m次 hello{3,}
                {m,} :至少m次
                {m,n} :m-n次
        边界:
                $ : 以某某结尾 
                ^ : 以某某开头
        分组:
                (ab)  
        贪婪模式: .*
        非贪婪(惰性)模式: .*?

re.I : 忽略大小写
re.M :多行匹配
re.S :单行匹配

re.sub(正则表达式, 替换内容, 字符串)
import re
#提取出python
key="javapythonc++php"
#####################################################################
#提取出hello world
key="<html><h1>hello world<h1></html>"
#####################################################################
#提取170
string = '我喜欢身高为170的女孩'
#####################################################################
#提取出http://和https://
key='http://www.baidu.com and https://boob.com'
#####################################################################
#提取出hit. 
key='bobo@hit.edu.com'#想要匹配到hit.
#####################################################################
#匹配sas和saas
key='saas and sas and saaas'
#####################################################################
key="javapythonc++php"
re.findall('python',key)
#提取出hello world
key="<html><h1>hello world<h1></html>"
re.findall('<h1>(.*?)<h1>',key)
#提取170
string = '我喜欢身高为170的女孩'
re.findall('\d+',string)
#提取出http://和https://
key='http://www.baidu.com and https://boob.com'
re.findall('https?://',key)
#提取出hit. 
key='bobo@hit.edu.com'#想要匹配到hit.
re.findall('h.*?\.',key)
#匹配sas和saas
key='saas and sas and saaas'
re.findall('sa{1,2}s',key)
  • 需求:使用正则将http://duanziwang.com/category/%E6%90%9E%E7%AC%91%E5%9B%BE/ 对应的段子的标题取出
url = 'http://duanziwang.com/category/搞笑图/'
#捕获到的是字符串形式的响应数据
page_text = requests.get(url=url,headers=headers).text

#数据解析
ex = '<div class="post-head">.*?<a href="http://duanziwang.com/\d+\.html">(.*?)</a></h1>'
re.findall(ex,page_text,re.S)#爬虫使用正则做解析的话re.S必须要使用

#持久化存储

bs4解析

  • 环境安装:

    • pip install bs4
    • pip install lxml
  • 解析原理

    • 1.实例化一个BeautifulSoup的对象,需要将等待被解析的页面源码数据加载到该对象中
    • 2.需要调用BeautifulSoup对象中相关的属性和方法进行标签定位和文本数据的提取
  • BeautifulSoup如何实例化

    • 方式1:BeautifulSoup(fp,'lxml'),将本地存储的一张html文件中的指定数据进行解析
    • 方式2:BeautifulSoup(page_text,'lxml'),将从互联网中爬取到的数据直接进行数据解析
  • 标签定位

    • soup.tagName:定位到第一个出现的tagName标签
    • 属性定位:根据指定的属性进行对应标签的定位
      • soup.find('tagName',attrName='value')
      • soup.find_all('tagName',attrName='value')
    • 选择器定位:
      • soup.select('选择器')
      • 层级选择器:
        • 大于号:表示一个层级
        • 空格:表示多个层级
  • 取文本

    • tag.string:取出直系的文本内容
    • tag.text:取出所有的文本内容
  • 取属性

    • tag['attrName']
from bs4 import BeautifulSoup
fp = open('./test.html','r',encoding='utf-8')
soup = BeautifulSoup(fp,'lxml')
soup
soup.div
# soup.find('div',class_='song')
# soup.find('a',id='feng')
# soup.find_all('div',class_='song')
# soup.select('.tang')
# soup.select('#feng')
soup.select('.tang li')
soup.p.string
soup.p.text
soup.find('div',class_='tang').text
a_tag = soup.select('#feng')[0]
a_tag['href']
main_url = 'http://www.shicimingju.com/book/sanguoyanyi.html'
page_text = requests.get(url=main_url,headers=headers).text

fp = open('./sanguo.txt','w',encoding='utf-8')

#数据解析
soup = BeautifulSoup(page_text,'lxml')
#解析出章节的标题&详情页的url
a_list = soup.select('.book-mulu > ul > li > a')
for a in a_list:
    title = a.string
    detail_url = 'http://www.shicimingju.com'+a['href']
    
    #捕获章节内容
    page_text_detail = requests.get(detail_url,headers=headers).text#获取了详情页的页面源码数据
    #数据解析:章节内容
    detail_soup = BeautifulSoup(page_text_detail,'lxml')
    div_tag = detail_soup.find('div',class_='chapter_content')

    content = div_tag.text
    
    fp.write(title+':'+content+'\n')
    print(title,'下载成功!!!')
fp.close()
  • 图片数据的爬取
    • 基于requests
    • 基于urllib
    • 区别:能不能实现UA伪装
#基于requests
url = 'http://pic.netbian.com/uploads/allimg/190902/152344-1567409024af8c.jpg'
img_data = requests.get(url=url,headers=headers).content#content返回的是二进制类型的数据
with open('./123.png','wb') as fp:
    fp.write(img_data)
#基于urllib
from urllib import request
request.urlretrieve(url=url,filename='./456.jpg')

xpath解析

  • 环境安装:
    • pip install lxml
  • 解析原理
    • 实例化一个etree的对象,且将被解析的页面源码数据加载到该对象中
    • 使用etree对象中的xpath方法结合着不同形式的xpath表达式进行标签定位和数据的提取
  • 实例化对象:
    • etree.parse('filePath'):将本地存储的一个html文件中的数据加载到实例化好的etree对象中
    • etree.HTML(page_text)
  • xpath表达式
    • 标签定位:

      • 最左侧的/:必须从根节点开始定位标签(几乎不用)
      • 非最左侧的/:表示一个层级
      • 最左侧的//:可以从任意位置进行指定标签的定位(最常用)
      • 非最左侧的//:表示多个层级
      • 属性定位://tagName[@attrName="value"]
      • 索引定位://tagNamne[index],索引是从1开始
      • //div[contains(@class, "ng")]
      • //div[starts-with(@class, "ta")]
    • 取文本

      • /text():取出直系的文本内容
      • //text():取出的是所有的文本
    • 取属性

      • /@attrName
from lxml import etree
tree = etree.parse('./test.html')
tree.xpath('/html/head/meta')
tree.xpath('/html//meta')
tree.xpath('//meta')
tree.xpath('/meta')#error
tree.xpath('//p')
tree.xpath('//div[@class="tang"]')
tree.xpath('//li[1]')
tree.xpath('//a[@id="feng"]/text()')[0]
tree.xpath('//div[@class="tang"]//text()')
tree.xpath('//a[@id="feng"]/@href')

重点:局部解析的时候./表示的含义

import os
url = 'http://pic.netbian.com/4kmeinv/'
page_text = requests.get(url=url,headers=headers).text

dirName = 'imgLibs'
if not os.path.exists(dirName):
    os.mkdir(dirName)

#数据解析
tree = etree.HTML(page_text)
#xpath是基于一整张页面源码进行数据解析
img_list = tree.xpath('//div[@class="slist"]/ul/li/a/img')

#局部数据解析
for img in img_list:
    #./表示的是当前标签,xpath的调用者就是当前标签
    img_src = 'http://pic.netbian.com'+img.xpath('./@src')[0]
    img_name = img.xpath('./@alt')[0]+'.jpg'
    img_name = img_name.encode('iso-8859-1').decode('gbk')
    
    filePath = './'+dirName+'/'+img_name
    request.urlretrieve(img_src,filename=filePath)
    print(img_name,'下载成功!!!')
#全站操作
#1.指定一个通用的url模板:用来生成不同页码对应的url,模板是不可变
url = 'http://pic.netbian.com/4kmeinv/index_%d.html'
dirName = 'imgLibs'
if not os.path.exists(dirName):
    os.mkdir(dirName)
    
for pageNum in range(1,6):
    if pageNum == 1:
        page_url = 'http://pic.netbian.com/4kmeinv/'
    else:
        page_url = format(url%pageNum)

    page_text = requests.get(url=page_url,headers=headers).text

    #数据解析
    tree = etree.HTML(page_text)
    #xpath是基于一整张页面源码进行数据解析
    img_list = tree.xpath('//div[@class="slist"]/ul/li/a/img')

    #局部数据解析
    for img in img_list:
        #./表示的是当前标签,xpath的调用者就是当前标签
        img_src = 'http://pic.netbian.com'+img.xpath('./@src')[0]
        img_name = img.xpath('./@alt')[0]+'.jpg'
        img_name = img_name.encode('iso-8859-1').decode('gbk')

        filePath = './'+dirName+'/'+img_name
        request.urlretrieve(img_src,filename=filePath)
        print(img_name,'下载成功!!!')

day03

  • 自己完成的任务
    • pyquery
      • 标签定位和数据的提取
    • urllib
      • 如何进行请求的发送

requests模块高级

  • 代理

  • 模拟登陆

    • 验证码
    • cookie
  • 代理(反爬机制)

    • 概念:代理服务器
    • 代理服务器的作用:
      • 拦截请求和响应,进行转发
    • 代理和爬虫之间的关联是什么?
      • 如果pc端的ip被禁掉后,我们就可以使用代理的机制更换请求的ip
    • 如何获取相关的代理服务器:
    • 匿名度:
      • 透明:知道你使用了代理也知道你的真实ip
      • 匿名:对方服务器知道你使用了代理机制,但是不知道你的真实ip
      • 高匿:对方服务器不知道你使用了代理机制更不知道你的真实ip
    • 类型:
      • http:只可以拦截转发http协议的请求
      • https:只可以转发拦截https的请求
  • 基本测试:代理的作用

    • 基于百度搜索ip,搜索到的页面中会显示该请求对应的ip地址
    • 语法结构
      • get/post(proxies={'http/https':'ip:port'})
import requests
from lxml import etree
headers = {
    'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36'
}
url = 'https://www.sogou.com/web?query=ip&_asf=www.sogou.com&_ast=&w=01019900&p=40040100&ie=utf8&from=index-nologin&s_from=index&sut=623&sst0=1578274254338&lkt=2%2C1578274253715%2C1578274253785&sugsuv=000AE8ACDDDAD0EB5DE49630C6588161&sugtime=1578274254338'
#代理机制对应的就是get或者post方法中的一个叫做proxies的参数
page_text = requests.get(url=url,headers=headers,proxies={'https':'14.134.184.156:39347'}).text
print(page_text)
  • 构建一个代理池
import random

#构建了一个代理池
all_ips = []
url = 'http://t.11jsq.com/index.php/api/entry?method=proxyServer.generate_api_url&packid=1&fa=0&fetch_key=&groupid=0&qty=21&time=1&pro=&city=&port=1&format=html&ss=5&css=&dt=1&specialTxt=3&specialJson=&usertype=2'
page_text = requests.get(url=url,headers=headers).text
tree = etree.HTML(page_text)
datas = tree.xpath('//body//text()')
for data in datas:
    dic = {
        'https':data
    }
    all_ips.append(dic)
all_ips[0]

#将西祠代理中的免费代理ip进行爬取
url = 'https://www.xicidaili.com/nn/%d'#定义了一个通用的url模板
datas = [] #存储解析到的所有的数据
for pageNum in range(1,16):
    new_url = format(url%pageNum)
    page_text = requests.get(url=new_url,headers=headers,proxies=random.choice(all_ips)).text
    tree = etree.HTML(page_text)
    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]
        dic = {
            'https':ip+':'+port
        }#{'https':'ip:port'}
        datas.append(dic)
print(len(datas))

模拟登陆

  • 为什么需要实现模拟登陆
    • 相关的页面是必须经过登陆之后才可现
  • 验证码的处理
    • 使用相关的打码平台进行验证码的动态识别
    • 打码平台:
    • 超级鹰的使用流程:
      • 注册【用户中心】身份的账号
      • 登陆
        • 创建一个软件
        • 下载示例代码
  • cookie的处理
#下载好的示例代码
#!/usr/bin/env python
# coding:utf-8

import requests
from hashlib import md5

class Chaojiying_Client(object):

    def __init__(self, username, password, soft_id):
        self.username = username
        password =  password.encode('utf8')
        self.password = md5(password).hexdigest()
        self.soft_id = soft_id
        self.base_params = {
            'user': self.username,
            'pass2': self.password,
            'softid': self.soft_id,
        }
        self.headers = {
            'Connection': 'Keep-Alive',
            'User-Agent': 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)',
        }

    def PostPic(self, im, codetype):
        """
        im: 图片字节
        codetype: 题目类型 参考 http://www.chaojiying.com/price.html
        """
        params = {
            'codetype': codetype,
        }
        params.update(self.base_params)
        files = {'userfile': ('ccc.jpg', im)}
        r = requests.post('http://upload.chaojiying.net/Upload/Processing.php', data=params, files=files, headers=self.headers)
        return r.json()

    def ReportError(self, im_id):
        """
        im_id:报错题目的图片ID
        """
        params = {
            'id': im_id,
        }
        params.update(self.base_params)
        r = requests.post('http://upload.chaojiying.net/Upload/ReportError.php', data=params, headers=self.headers)
        return r.json()


# if __name__ == '__main__':
# 	chaojiying = Chaojiying_Client('超级鹰用户名', '超级鹰用户名的密码', '96001')	#用户中心>>软件ID 生成一个替换 96001
# 	im = open('a.jpg', 'rb').read()													#本地图片文件路径 来替换 a.jpg 有时WIN系统须要//
# 	print chaojiying.PostPic(im, 1902)												#1902 验证码类型  官方网站>>价格体系 3.4+版 print 后要加()



#自己封装一个识别验证码的函数
def transformCode(imgPath,imgType):
    chaojiying = Chaojiying_Client('fh0821', '54874428', '903087')
    im = open(imgPath, 'rb').read()
    return chaojiying.PostPic(im,imgType)['pic_str']

  • cookie的处理
    • 手动处理
      • 将请求携带的cookie封装到headers中
    • 自动处理
      • session对象。该对象和requests都可以进行get和post的请求的发送。如果使用session对象在进行请求发送过程中,产生了cookie,则cookie会被自动存储到session对象中。如果cookie存储到了session对象中后,再次使用session进行请求发送,则该次请求就是携带者cookie发送的请求。
      • 在使用session处理cookie的时候,session对象最少需要发送几次请求?
        • 两次。第一次请求是为了获取和存储cookie,第二次请求才是携带cookie进行的请求发送。
#需求:爬取雪球网https://xueqiu.com/,中的新闻标题和内容
#实例化一个session对象
session = requests.Session()
#试图将cookie获取且存储到session对象中
session.get('https://xueqiu.com/',headers=headers)
url = 'https://xueqiu.com/v4/statuses/public_timeline_by_category.json?since_id=-1&max_id=20361817&count=15&category=-1'
session.get(url=url,headers=headers).json()#携带cookie进行的请求发送

session = requests.Session()


#验证码的识别
first_url = 'https://so.gushiwen.org/user/login.aspx?from=http://so.gushiwen.org/user/collect.aspx'
page_text = session.get(first_url,headers=headers).text
#解析出验证码图片的src的属性值
tree = etree.HTML(page_text)
code_img_src = 'https://so.gushiwen.org'+tree.xpath('//*[@id="imgCode"]/@src')[0]
#对验证码图片地址进行请求发送
code_img_data = session.get(code_img_src,headers=headers).content#产生了cookie
with open('./code.jpg','wb') as fp:
    fp.write(code_img_data)


#使用基于超级鹰实现的函数进行验证码的识别
code_img_text = transformCode('./code.jpg',1902)
print(code_img_text)

url = 'https://so.gushiwen.org/user/login.aspx?from=http%3a%2f%2fso.gushiwen.org%2fuser%2fcollect.aspx'
data = {
    '__VIEWSTATE': 'WQlcl775IHe1sjsai6HoahnPHU79MSS7ZyjzWaONdzwJSlR76Ndfm9mikHQVMTSwHZKIQ+fC8SeVGIU6b5AIb1spoHSnuoINERq+PdbI7vv/3+qaXK10HtmIw7w=',
    '__VIEWSTATEGENERATOR': 'C93BE1AE',
    'from': 'http://so.gushiwen.org/user/collect.aspx',
    'email': 'www.zhangbowudi@qq.com',
    'pwd': 'bobo328410948',
    'code': code_img_text,
    'denglu': '登录',
}
#点击登陆按钮对应的请求发送操作
page_text = session.post(url=url,headers=headers,data=data).text
with open('./gushiwen.html','w',encoding='utf-8') as fp:
    fp.write(page_text)


  • 遇到了动态变化的请求参数如何处理?

    • 通常情况下,动态变化的请求参数的值都会被隐藏在前台页面中
    • 基于抓包工具进行全局搜索(基于js逆向获取相关的数据值)
  • 回顾

    • 代理
      • 概念
      • 作用
        • 请求转发
      • 实现:
        • get/post(proxies={'https':'ip:port'})
      • 代理池:大列表
    • 验证码的识别
      • 打码平台
    • cookie
      • 手动处理
      • 自动处理:session
    • 动态变化的请求参数
      • 前台页面
      • 全局搜索
  • 总结反爬机制:

    • robots
    • UA检测
    • 动态加载数据的捕获
    • 代理
    • 验证码
    • 动态变化的请求参数
    • cookie
    • 图片懒加载
      • 伪属性
  • 需求:爬取文本数据将其通过百度ai的语音合成功能将文字合成成音频文件,将将音频文件部署到flask服务器中进行播放

from aip import AipSpeech

""" 你的 APPID AK SK """
APP_ID = '17272609'
API_KEY = 'h3Grp2xXGG0VeSAKlDL9gc4Q'
SECRET_KEY = 'WEEeDpICnzifwBAGF4QW4QiGgSb1u3ND'
client = AipSpeech(APP_ID, API_KEY, SECRET_KEY)

#文本数据的爬取
url = 'http://duanziwang.com/category/%E4%B8%80%E5%8F%A5%E8%AF%9D%E6%AE%B5%E5%AD%90/2/'
page_text = requests.get(url,headers=headers).text

tree = etree.HTML(page_text)
article_list = tree.xpath('/html/body/section/div/div/main/article')
fileName = 1
for article in article_list:
    fileName += 1
    content = article.xpath('./div[1]/h1/a/text()')[0]
    #数据爬取到了,基于语音合成将其转换成音频文件
    result  = client.synthesis(content, 'zh', 1, {
    'vol': 5,
    'per':4,
    'spd':3,
})
    # 识别正确返回语音二进制 错误则返回dict 参照下面错误码
    if not isinstance(result, dict):
        filePath = str(fileName)+'.mp3'
        with open(filePath, 'wb') as f:
            f.write(result)
        print(filePath,'写入成功!!!')


day04

#演示代码
from selenium import webdriver
from time import sleep

# 后面是你的浏览器驱动位置,记得前面加r'','r'是防止字符转义的
driver = webdriver.Chrome(r'./chromedriver.exe')
# 用get打开百度页面
driver.get("http://www.baidu.com")
# 查找页面的“设置”选项,并进行点击
driver.find_elements_by_link_text('设置')[0].click()
sleep(2)
# # 打开设置后找到“搜索设置”选项,设置为每页显示50条
driver.find_elements_by_link_text('搜索设置')[0].click()
sleep(2)

# 选中每页显示50条
m = driver.find_element_by_id('nr')
sleep(2)
m.find_element_by_xpath('//*[@id="nr"]/option[3]').click()
m.find_element_by_xpath('.//option[3]').click()
sleep(2)

# 点击保存设置
driver.find_elements_by_class_name("prefpanelgo")[0].click()
sleep(2)

# 处理弹出的警告页面   确定accept() 和 取消dismiss()
driver.switch_to_alert().accept()
sleep(2)
# 找到百度的输入框,并输入 美女
driver.find_element_by_id('kw').send_keys('美女')
sleep(2)
# 点击搜索按钮
driver.find_element_by_id('su').click()
sleep(2)
# 在打开的页面中找到“Selenium - 开源中国社区”,并打开这个页面
driver.find_elements_by_link_text('美女_百度图片')[0].click()
sleep(3)

# 关闭浏览器
driver.quit()

selenium

  • 概念:基于浏览器自动化的模块

    • appnium:基于手机应用的自动化模块
  • 环境的安装:

    • pip install selenium
  • 爬虫之间的关联?

    • 实现模拟登陆
    • 便捷帮我们捕获到动态加载的数据(可见即可得)
  • selenium的基本操作

    • 必须提供对应浏览器的驱动程序
    • 谷歌浏览器驱动程序下载地址:
    • 实例化一个浏览器对象
      • bro = webdriver.Chrome(executable_path='驱动程序的路径')
    • 标签定位:
      • find系列的函数
    • senk_keys():向制定标签中录入数据
    • click()
    • js注入:execute_script('jsCode')
    • page_source:返回当前页面的页面源码数据(包含动态加载数据)
  • 缺点:

    • 数据爬取的效率低下
from selenium import webdriver
from time import sleep
#实例化浏览器对象
bro = webdriver.Chrome(executable_path='./chromedriver.exe')
#制定一些自动化的操作

#发起请求
bro.get('https://www.jd.com/')

#标签定位
search_tag = bro.find_element_by_id('key')
#向文本框中录入数据
search_tag.send_keys('mac pro')
sleep(2)
btn = bro.find_element_by_xpath('//*[@id="search"]/div/div[2]/button')
btn.click()
sleep(2)
#js注入execute_script('jsCode')
bro.execute_script('window.scrollTo(0,document.body.scrollHeight)')
sleep(2)
#page_source:返回当前页面的页面源码数据(包含动态加载数据)
print(bro.page_source)

#关闭浏览器
bro.quit()
from lxml import etree
#使用selenium捕获药监总局的动态加载数据
bro = webdriver.Chrome(executable_path='./chromedriver.exe')
bro.get('http://125.35.6.84:81/xk/')
sleep(1)
#第一页的页面源码数据
page_text = bro.page_source

all_page_text = [page_text]

for i in range(1,4):
    a_tag = bro.find_element_by_xpath('//*[@id="pageIto_next"]')
    a_tag.click()
    sleep(1)
    page_text = bro.page_source
    all_page_text.append(page_text)
    
for page_text in all_page_text:
    tree = etree.HTML(page_text)
    li_list = tree.xpath('//*[@id="gzlist"]/li')
    for li in li_list:
        name = li.xpath('./dl/@title')[0]
        print(name)
    
bro.quit()

线程池,异步协程

  • 线程池

    • 导包:from multiprocessing.dummy import Pool
    • pool.map(callback,alist)
      • 让callback可以异步将alist中的列表元素进行某种形式的操作
      • 注意事项:callback必须要有一个参数、
  • 主要是被应用在耗时的操作

  • 单线程+多任务的异步协程

    • 特殊函数

      • 如果一个函数的定义被async关键字修饰后,则该函数就是一个特殊的函数。
      • 特殊之处:
        • 该函数被调用后函数内部的实现语句不会被立即执行。
        • 该函数会返回一个协程对象
    • 协程:

      • 对象。当特殊的函数被调用后,该函数就会返回一个协程对象。
      • 协程对象 == 特殊函数
    • 任务对象

      • 就是对协程对象的进一步封装(就是一个高级的协程对象)
      • 任务对象协程对象特殊函数
      • 绑定回调:
        • task.add_done_callback(funcName)
        • funName这个回调函数必须要带一个参数,这个参数表示的就是当前的任务对象
          • 参数.result():表示的就是当前任务对象对应的特殊函数的返回值
    • 事件循环对象:

      • 创建事件循环对象
        • asyncio.get_event_loop()
      • 需要将任务对象注册到该事件循环对象中且启动事件循环
        • loop.run_until_complete(task)
    • 等待(await):当阻塞操作结束后让loop回头执行阻塞之后的代码

    • 挂起(wait()):将当前的任务对象交出cpu的使用权

    • 【重点注意事项】:

  • 在特殊函数实现内部不可以出现不支持异步的模块代码,否则会中断整个的异步效果

    • aiohttp:

      • requests不支持异步,不可以出现在特殊函数内部。

      • aiohttp:支持异步的网络请求模块

        • pip install aiohttp
      • 代码的编写:

        • 写出基本架构
          with aiohttp.ClientSession() as sess:
          #with sess.get/post(url=url,headers=headers,data/params,proxy="http://ip:port") as response:
          with sess.get(url=url) as response:
          #text():获取字符串形式的响应数据
          #read():获取bytes类型的响应数据
          page_text = response.text()

                  return page_text
          
        • 补充细节

          • 在每一个with前加上async
          • 在每一个阻塞操作前加上await关键字
            • 代码参照完整代码
      • 完整代码:
        async with aiohttp.ClientSession() as sess:

        with sess.get/post(url=url,headers=headers,data/params,proxy="http://ip:port") as response:

        async with await sess.get(url=url) as response:
        #text():获取字符串形式的响应数据

        read():获取bytes类型的响应数据

        page_text = await response.text()

         return page_text
        
  • 解析到带html标签的文本内容

    • bs4

day05

回顾

  • 单线程+多任务的异步协程

    • 特殊的函数
      • 调用后实现内部的程序语句不会被立即执行
      • 调用后返回一个协程对象
    • 协程对象
      • 协程特殊函数一组指定的操作
    • 任务对象
      • 高级的协程对象
      • 绑定回调函数
        • task.add_done_callback(func)
        • func:
          • 必须要有一个参数(当前的任务对象)
          • 参数.result()表示的就是特殊函数的返回值
      • 任务对象 == 一组指定的操作
    • 事件循环对象
      • 创建一个eventloop对象
      • 作用:
        • 必须要装载一个或者多个任务对象(任务对象是需要注册到eventloop)
        • 启动事件循环对象
          • 可以异步的执行其内部注册的每一个任务对象对应的指定操作
    • 等待await:确保eventloop一定会执行阻塞操作
    • 挂起:让当前发生阻塞的任务对象交出cpu的使用权
      • asyncio.wait(tasks)
    • 重点:
      • 特殊函数内部不可以出现不支持异步模块的代码
    • aiohttp:支持异步的网络请求模块
      • 使用使用上下文机制(with...as)
      • 实例化一个请求对象(ClientSession())
      • get/post()进行请求发送。(阻塞操作)
      • 获取响应数据(阻塞操作)
        • response.text():字符串
        • response.read():byte
  • selenium

    • 动作链
    • 无头浏览器
    • 规避检测
      • 浏览器托管
    • 12306的模拟登陆
  • 动作链

    • from selenium.webdriver import ActionChains
    • NoSuchElementException:没有定位到指定的标签
      • 定位的标签是存在于一张嵌套的子页面中,如果想定位子页面中的指定标签的话需要:
        • bro.switch_to.frame('iframe标签id的属性值'):将当前浏览器页面切换到指定的子页面的范围中
    • 针对指定的浏览器实例化一个动作链对象
      • action = ActionChains(bro)
    • action.click_and_hold(tagName)
    • move_by_offset(10,15)
    • perform()是的动作链立即执行
from selenium import webdriver
from selenium.webdriver import ActionChains
from time import sleep
# 后面是你的浏览器驱动位置,记得前面加r'','r'是防止字符转义的
bro = webdriver.Chrome('./chromedriver.exe')
bro.get('https://www.runoob.com/try/try.php?filename=jqueryui-api-droppable')

#标签定位
bro.switch_to.frame('iframeResult')
div_tag = bro.find_element_by_id('draggable')

#需要使用ActionChains定制好的行为动作
action = ActionChains(bro)#针对当前浏览器页面实例化了一个动作链对象
action.click_and_hold(div_tag)#点击且长按指定的标签

for i in range(1,7):
    action.move_by_offset(10,15).perform()#perform()是的动作链立即执行
    sleep(0.5)
    
bro.quit()
    

  • 无头浏览器
    • 没有可视化界面的浏览器
    • phantomJS:无头浏览器
    • 谷歌无头浏览器:
      • 就是你本机安装的谷歌浏览器,只是需要通过代码进行相关配置就可以变成无头浏览器
from selenium.webdriver.chrome.options import Options
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.baidu.com/')
sleep(1)
bro.save_screenshot('./1.png')#截屏

print(bro.page_source)

selenium规避检测

    • 浏览器托管
      • 环境配置:
        • 1.本机谷歌浏览器驱动程序所在的目录的路径添加到环境变量中
        • 2.使用本机谷歌的驱动程序开启一个浏览器
          • chrome.exe --remote-debugging-port=9222 --user-data-dir="C:\selenum\AutomationProfile"
            • 9222:端口(任意)
            • "C:\selenum\AutomationProfile":已经事先存在的一个空目录
      • 使用如下代码接管目前打开的浏览器:
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
 
chrome_options = Options()
chrome_options.add_experimental_option("debuggerAddress", "127.0.0.1:9222")


bro = webdriver.Chrome(executable_path='./chromedriver.exe',chrome_options=chrome_options)#代码托管代开的浏览器,不会实例化一个新的浏览器。
bro.get('https://kyfw.12306.cn/otn/login/init')

12306模拟登陆

#pip install Pillow
from PIL import Image
from selenium.webdriver import ActionChains
from selenium import webdriver
#识别验证码的函数
def transformCode(imgPath,imgType):
    chaojiying = Chaojiying_Client('13614167787', '13614167787', '903126')
    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.find_element_by_id('username').send_keys('xxxxxxx')
bro.find_element_by_id('password').send_keys('12345465')

#验证码的点击操作
bro.save_screenshot('main.png')#将页面当做图片保存到本地
#将单独的验证码图片从main.png中裁剪下载
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
# print(location,size)
#裁剪的范围(验证码图片左下角和右上角两点坐标)
rangle = (int(location['x']),int(location['y']),int(location['x']+size['width']),int(location['y']+size['height']))
#使用Image类根据rangle裁剪范围进行验证码图片的裁剪
i = Image.open('./main.png')
frame = i.crop(rangle)#验证码对应的二进制数据
frame.save('./code.png')

result = transformCode('./code.png',9004)#99,71|120,140
#99,71|120,140 == [[99,71],[120,140]]
all_list = []#[[99,71],[120,140]]
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 data in all_list:
    x = data[0]#11
    y = data[1]#22
    ActionChains(bro).move_to_element_with_offset(img_tag,x,y).click().perform()
    sleep(1)
    
sleep(2)
bro.find_element_by_id('loginSub').click()

bro.quit()
#下载好的示例代码
#!/usr/bin/env python
# coding:utf-8

import requests
from hashlib import md5

class Chaojiying_Client(object):

    def __init__(self, username, password, soft_id):
        self.username = username
        password =  password.encode('utf8')
        self.password = md5(password).hexdigest()
        self.soft_id = soft_id
        self.base_params = {
            'user': self.username,
            'pass2': self.password,
            'softid': self.soft_id,
        }
        self.headers = {
            'Connection': 'Keep-Alive',
            'User-Agent': 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)',
        }

    def PostPic(self, im, codetype):
        """
        im: 图片字节
        codetype: 题目类型 参考 http://www.chaojiying.com/price.html
        """
        params = {
            'codetype': codetype,
        }
        params.update(self.base_params)
        files = {'userfile': ('ccc.jpg', im)}
        r = requests.post('http://upload.chaojiying.net/Upload/Processing.php', data=params, files=files, headers=self.headers)
        return r.json()

    def ReportError(self, im_id):
        """
        im_id:报错题目的图片ID
        """
        params = {
            'id': im_id,
        }
        params.update(self.base_params)
        r = requests.post('http://upload.chaojiying.net/Upload/ReportError.php', data=params, headers=self.headers)
        return r.json()


# if __name__ == '__main__':
# 	chaojiying = Chaojiying_Client('超级鹰用户名', '超级鹰用户名的密码', '96001')	#用户中心>>软件ID 生成一个替换 96001
# 	im = open('a.jpg', 'rb').read()													#本地图片文件路径 来替换 a.jpg 有时WIN系统须要//
# 	print chaojiying.PostPic(im, 1902)												#1902 验证码类型  官方网站>>价格体系 3.4+版 print 后要加()


移动端数据的捕获

  • 三方的抓包工具

    • fiddler(window)
    • 青花瓷(mac)
    • miteproxy
  • 抓包工具就是代理服务器

    • 拦截和转发请求and响应
  • fiddler的基本使用

    • 安装
      • 默认情况下装好的fiddler只可以捕获到http协议的请求
    • 配置:
      • 进行fiddler证书的本机安装
        • tools->options->HTTPS->
          image.png
  • 配置fiddler让其可以捕获其它设备发起的请求
    image.png

    • 将fiddler重新启动
  • 需要在手机中安装好fiddler的证书

    • 将fiddler所在的机器和手机设定到一个网段下(pc开启一个热点手机连接)
    • 在手机的浏览器中访问:fiddler所在机器的ip:fidder的端口(192.168.1.123:8887),就可以看到如下页面(提供了一个证书下载的功能):
      image.png
  • 将下载好的证书信任且安装到手机中

  • 在手机中配置代理

    • 配置的代理ip:fiddler所在机器的ip地址
    • 代理端口:fiddler自己的端口

js加密、解密、混淆

  • url:https://www.aqistudy.cn/html/city_detail.html

  • 分析:

    • 空气指标的数据是动态加载出来
      • 修改了搜索条件后点击搜索按钮会发起ajax请求,请求到我们想要的指标数据。
    • 从上一步定位到的数据包中提取出url,请求方式,请求参数
      • url和请求方式可以拿来直接用
      • 请求参数是动态变化且加密
    • 响应数据也是加密的密文数据
  • 找到点击搜索按钮发起的ajax请求对应的代码

    • 基于火狐浏览器的开发者工具找到搜索按钮对应点击事件绑定的js函数是哪个
      • getData(),该函数的实现
        • type=HOUR:以小时为单位进行数据的查询
        • 调用了另两个函数:getAQIData(), getWeatherData()
        • 并没有找到ajax请求对应的的代码
      • 分析getAQIData&getWeatherData:
        • 这两个函数的实现几乎一致,唯一的区别是
          • var method = 'GETDETAIL';
          • var method = 'GETCITYWEATHER';
        • 也没有找到ajax请求对应的代码,但是发现了另一个函数的调用:
          • getServerData(method, param, function(obj),0.5 )
            • method:
              • 'GETDETAIL'
              • 'GETCITYWEATHER'
            • param是一个字典,有四组键值对:
              • city;
              • type;
              • startTime;
              • endTime;
        • 分析getServerData函数的实现:
          • 基于抓包工具进行全局搜索,定位到了一个指定的数据包,出现了getServerData关键词,这个关键词对应的js代码被加密了
          • JS混淆:将js中的核心代码加密
          • JS反混淆:
            • 暴力破解:
            • 分析反混淆后的getServerData函数的实现代码:
              • 终于发现了ajax请求对应的代码:
                • getParam(method, object)返回动态变化且加密的请求参数d的值。
                  • method == method
                  • object == param
                • decodeData(data):接受加密的响应数据返回解密后的明文数据
                  • data:加密的响应数据
        • js逆向:
          • 自动逆向:
            • PyExecJS介绍:PyExecJS 是一个可以使用 Python 来模拟运行 JavaScript 的库。
              • 一定实现在本机装好nodejs的开发环境
            • 我们需要pip install PyExecJS对其进行环境安装。
#模拟执行js函数获取动态变化且解密的请求参数d的值
import execjs
node = execjs.get()
 
# Params
method = 'GETCITYWEATHER'
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函数
print(params)
  • 对ajax的url携带d请求参数进行post请求的发送可以获取加密的响应数据
import execjs
import requests

node = execjs.get()
 
# Params
method = 'GETCITYWEATHER'
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)

#发起post请求
url = 'https://www.aqistudy.cn/apinew/aqistudyapi.php'
response_text = requests.post(url, data={'d': params}).text
print(response_text)
#将密文的响应数据进行解密:模拟调用decodeData(data)
import execjs
import requests

node = execjs.get()
 
# Params
method = 'GETCITYWEATHER'
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)

#发起post请求
url = 'https://www.aqistudy.cn/apinew/aqistudyapi.php'
response_text = requests.post(url, data={'d': params}).text
# #对加密的响应数据进行解密
jss = 'decodeData("{0}")'.format(response_text)
print(jss)
decrypted_data = ctx.eval(jss)
print(decrypted_data)

day06

回顾

  • 问题
    • 1.空气参数问题
    • 2.代理速度慢
    • 3.截图问题
      • 分辨率
  • selenium
    • 动作链
    • 浏览器托管
      • 规避检测
    • 无头浏览器
      • phantomJS
      • 谷歌无头
      • pyppteer
    • appnium:
      • 基于手机app的自动化的模块
    • 和爬虫之间的关联
      • 模拟登陆
      • 便捷的捕获到动态加载的数据
  • js解密
    • js混淆
      • 对js核心代码进行加密
    • js逆向
      • 将js代码转换成python代码
      • 手动逆向
      • 自动逆向:
        • PyExcelJS

scrapy框架

​ - 框架:就是一个具有很强的通用性且封装一些通用实现方法(功能)的一个项目模板。
​ - scrapy(异步):
​ - 高性能的网络请求
​ - 数据解析
​ - 持久化存储
​ - 全站数据爬取
​ - 深度爬取
​ - 分布式

环境的安装

a. pip3 install wheel

b. 下载twisted http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted

c. 进入下载目录,执行 pip3 install Twisted‑17.1.0‑cp35‑cp35m‑win_amd64.whl

d. pip3 install pywin32

e. pip3 install scrapy

scrapy的基本使用

  • 创建一个工程

    • scrapy startproject proName
    • cd proName
  • 创建一个爬虫文件(py源文件)

    • scrapy genspider spiderName www.xxx.com
  • 基本的配置:settings.py

    • UA伪装
    • robots协议的不遵从
    • 指定日志等级
      • LOG_LEVEL = 'ERROR'
  • 执行工程

    • scrapy crawl spiderName
  • 数据解析

    • response.xpath('xpath表达式')
    • 于etree的不同之处:
      • 取文本/属性:返回的是一个Selector对象,文本数据是存储在该对象中,
        可以调用extract()/extract_first()取出字符串数据
    • 常用操作:
      • 直接使用xpath返回列表调用extract()/extract_first()
        • 如果列表元素只有一个则调用extract_first(),返回的字符串
        • 如果列表元素为多个则调用extract(),返回的是列表,列表里装的是字符串
    # -*- coding: utf-8 -*-
    import scrapy
    
    
    class FirstSpider(scrapy.Spider):
        #爬虫文件的名称:当前爬虫文件的唯一标识
        name = 'first'
        #允许的域名:
        # allowed_domains = ['www.baidu.com']
        #起始的url列表
        #作用:可以将内部的列表元素进行get请求的发送
        start_urls = ['http://www.duanziwang.com/']
    
        #数据解析,方法调用的次数是由start_urls元素个数决定
        def parse(self, response):
            article_list = response.xpath('/html/body/section/div/div/main/article')
            for article in article_list:
                # title = article.xpath('./div[1]/h1/a/text()').extract_first()#返回字符串
                title = article.xpath('./div[1]/h1/a/text()').extract()#返回列表
                title = ''.join(title)
                print(title)
    
    
  • 持久化存储

    • 基于终端指令的持久化存储
      • 只可以将parse方法的返回值存储到指定后缀(csv)文本文件中
      • 指令:scrapy crawl spiderName -o filePath
    class DuanziSpider(scrapy.Spider):
        name = 'duanzi'
        # allowed_domains = ['www.xxx.com']
        start_urls = ['http://www.duanziwang.com/']
    
        start_urls请求发送的原理
        def start_requests(self):#父类方法
            for url in self.start_urls:
                #发起get请求
                yield scrapy.Request(url=url,callback=self.parse)
    
    
        基于终端指令持久化存储
        def parse(self, response):
            article_list = response.xpath('/html/body/section/div/div/main/article')
            all_datas = []
            for article in article_list:
                title = article.xpath('./div[1]/h1/a/text()').extract_first()
                content = article.xpath('./div[2]/p//text()').extract()
                content = ''.join(content)
                dic = {
                    'title':title,
                    'content':content
                }
                all_datas.append(dic)
            return all_datas
    
    • 基于管道的持久化存储
      • 实现流程:
        1.数据解析(爬虫文件)
        2.在item类中定义相关的属性(items.py)
        - fieldName = scrapy.Field()
        3.将解析的数据存储封装到item类型的对象中(爬虫文件)
        - item['fieldName'] = value #给item对象的fieldName属性赋值
        4.将item对象提交给管道(爬虫文件)
        - yield item #只可以将item提交给优先级最高的管道
        5.在管道中接收item,可以将item中存储的数据进行任意形式的持久化存储(pipelines.py)
        - process_item():负责接收item对象且对其进行持久化存储
        6.在配置文件中开启管道机制
        - ITEM_PIPELINES = {
        #300:表示的是优先级,数值越小优先级越高
        'duanziPro.pipelines.DuanziproPipeline': 300,
        }
    • 细节处理
      • 管道文件中的管道类表示的是什么

        • 一个管道类对应的就是一种存储形式
        • 如果想要实现数据备份,则需要使用多个管道类(多种存储形式=》mysql/redis)
      • process_item中的return item:

        • 将item传递给下一个即将被执行的管道类
    # -*- coding: utf-8 -*-
    
    # Define your item pipelines here
    #
    # Don't forget to add your pipeline to the ITEM_PIPELINES setting
    # See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html
    
    import pymysql
    from redis import Redis
    class DuanziproPipeline(object):
        #重写父类的该方法:该方法只会在爬虫开始的时候执行一次
        fp = None
        def open_spider(self,spider):
            print('i am open_spider()')
            self.fp = open('./duanzi.txt','w',encoding='utf-8')
        def close_spider(self,spider):
            print('i am close_spider()')
            self.fp.close()
    
        #接收item对象:procee_item方法每调用一次可以接收一个item对象
        #item参数:接收到的某一个item对象
        def process_item(self, item, spider):
            title = item['title']
            content = item['content']
            self.fp.write(title+':'+content+'\n')
            return item#将item转交给下一个即将被执行的管道类
    
    #数据存储到mysql
    class MysqlPileLine(object):
        conn = None
        cursor = None
        def open_spider(self,spider):
            self.conn = pymysql.Connect(host='127.0.0.1',port=3306,user='root',password='123',db='spider',charset='utf8')
        def process_item(self, item, spider):
            title = item['title']
            content = item['content']
    
            self.cursor = self.conn.cursor()
            sql = 'insert into duanzi values ("%s","%s")'%(title,content)
            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()
    
    class RedisPipeLine(object):
        conn = None
        def open_spider(self,spider):
            self.conn = Redis(host='127.0.0.1',port=6379)
        def process_item(self,item,spider):
            self.conn.lpush('duanziList',item)
            #报错:pip install redis==2.10.6
    
    
    
    
    

手动get请求发送

  • yield scrapy.Request(url,callback)
    • url:指定好的请求的url
    • callback:callback指定的回调函数一定会被执行(数据解析)
  • post请求的发送:
    • yield scrapy.FormRequest(url,callback,formdata)
class DuanziSpider(scrapy.Spider):
    name = 'duanzi'
    # allowed_domains = ['www.xxx.com']
    start_urls = ['http://www.duanziwang.com/']
    #手动请求的发送:对其他页码的数据进行请求操作
    url = 'http://www.duanziwang.com/page/%d/'
    pageNum = 2

    def parse(self, response):
        article_list = response.xpath('/html/body/section/div/div/main/article')
        all_datas = []
        for article in article_list:
            title = article.xpath('./div[1]/h1/a/text()').extract_first()
            content = article.xpath('./div[2]/p//text()').extract()
            content = ''.join(content)
            #实例化item对象
            item = DuanziproItem()
            #通过中括号的形式访问属性且给其赋值
            item['title'] = title
            item['content'] = content
            yield item
        if self.pageNum < 4:
            #其他页码的url
            new_url = format(self.url%self.pageNum)
            self.pageNum += 1
            #callback:指定解析的方法是哪一个
            yield scrapy.Request(url=new_url, callback=self.parse)

yield在scrapy中的使用

  • 向管道提交item对象:yield item

  • 手动请求发送:yield scrapy.Request(url,callback)

  • 问题:如何将start_urls中的元素进行post请求的发送

    • 重写start_requests方法
      def start_requests(self):#父类方法
      for url in self.start_urls:
      #发起get请求
      yield scrapy.FormRequest(url=url,callback=self.parse,formdata)

scrapy的五大核心组件

引擎(Scrapy)

​ 用来处理整个系统的数据流处理, 触发事务(框架核心)

调度器(Scheduler)

​ 用来接受引擎发过来的请求, 压入队列中, 并在引擎再次请求的时候返回. 可以想像成一个URL(抓取网页的网址或者说是链接)的优先队列, 由它来决定下一个要抓取的网址是什么, 同时去除重复的网址

下载器(Downloader)

​ 用于下载网页内容, 并将网页内容返回给蜘蛛(Scrapy下载器是建立在twisted这个高效的异步模型上的)

爬虫(Spiders)

​ 爬虫是主要干活的, 用于从特定的网页中提取自己需要的信息, 即所谓的实体(Item)。用户也可以从中提取出链接,让Scrapy继续抓取下一个页面

项目管道(Pipeline)

​ 负责处理爬虫从网页中抽取的实体,主要的功能是持久化实体、验证实体的有效性、清除不需要的信息。当页面被爬虫解析后,将被发送到项目管道,并经过几个特定的次序处理数据。

day07

内容回顾

  • 工程的创建
    • cd
  • 数据解析
    • 请求发送:start_urls
    • 函数:parse
    • start_requests方法:
      • 就是讲start_urls中的url进行get请求的发送
    • xpath进行的解析
      • etree解析的区别:
        • extract():有多个元素的列表中
        • extract_first():只有一个元素的列表中
  • 持久化存储
    • 基于终端指令
      • 只可以将数据存储到指定后缀的文本文件中
      • 存储的数据必须为parse方法的返回值
      • 指令:scrapy crawl spiderName -o filePath
    • 基于管道
      • 1.解析数据
      • 2.在item类中定义相关的属性
        • name = scrapy.Field()
      • 3.解析到的数据存储到item类型的对象
      • 4.将item提交给管道
        • yield item
      • 5.在管道中接受item(process_item),进行任意形式的持久化存储
      • 6.开启管道
    • 注意事项:
      • 一个管道类对应什么
        • 一个管道类对应一种形式的存储
      • process_item=》return item
  • 手动请求发送
    • get:
      • yield scrapy.Request(url,callback)
    • post:
      • yield scrapy.FormRequest(url,callback)
  • 核心组件
    • 引擎
      • 事务的触发
    • spider
      • 产生url,发起请求,数据解析
    • 调度器
      • 过滤器
      • 队列
    • 管道
    • 下载器

今日内容

请求传参

  • 作用:实现深度爬取
  • 什么深度爬取:
    • 爬取的数据没有存在于同一张页面中
  • 在进行手动请求发送的时候可以将一个meta字典传递给callback指定的回调函数
    • yield scrapy.Request(url,callback,meta)
    • 在回调函数中如何接收meta:
      • response.meta['key'] #将meta字典中key对应的value值取出

中间件

  • 爬虫中间件:位于引擎和Spider中间
  • 下载中间件:位于引擎和下载器中间(推荐)
    • 作用:拦截请求和响应
    • 为什么需要拦截请求?
      • UA伪装(篡改请求的头信息)
        • process_request
          • request.headers['User-Agent'] = 'xxx'
      • 设置代理
        • process_exception:
          • request.meta['proxy'] = 'http://ip:port'
          • return request:将修正后的请求对象进行重新发送
    • 为什么需要拦截响应?
      • 篡改响应数据
    • 网易新闻数据爬取
      • 实现流程:
        • 1.解析出5个板块对应页面的url
        • 2.对五个板块的url发起请求
        • 3.获取板块的页面源码数据
          • 问题:板块的页面源码数据(响应数据)中没有包含新闻的标题和新闻详情页的url(动态加载的数据)
          • 处理:将响应对象的响应数据进行篡改,将其该为包含动态加载数据的响应数据
      • selenium帮助我们捕获到包含了动态加载的响应数据
        • selenium在scrapy中如何使用
          • 实例化一个浏览器对象(爬虫文件中)
          • 在中间件的process_response中进行selenium后续的操作
          • 在爬虫文件的爬虫类中重写一个closed(self,spider),关闭浏览器

作业:

  • 将网易新闻的新闻数据进行爬取

    • title
    • content
  • 基于百度AI将新闻的类型和关键字进行提取

  • 持久化存储:

    • mysql

      • 库表:
        • 新闻标题
        • 新闻内容
        • 新闻类型
        • 新闻关键字
    • error:

      • \x0x无法被gbk识别
        • s.replase('\x0x','')

day08

scrapy如何爬取图片数据(ImagePileLine)

- 爬虫文件中将二进制资源的url进行爬取和解析,将其存储到item中向管道提交
    - 在管道文件中指定对应的管道类
    - 父类:from scrapy.pipelines.images import ImagesPipeline
    - 配置文件中:
        - 自动创建一个指定的文件夹:IMAGES_STORE = './imgsLib'
    - 自定义一个基于ImagesPipeline该父类的管道类,重写如下三个方法:
            #发起请求
            def get_media_requests(self,item,info):
                print(item)
                img_src = item['img_src']
                #请求传参,将meta字典传递给了file_path这个方法
                yield scrapy.Request(url=img_src,meta={'item':item})
            #定制get_media_request请求到数据持久化存储的路径(文件夹的路径+文件名称)
            def 							file_path(self,request,response=None,info=None):
                #通过request.meta接收请求传参传递过来的meta字典
                img_name = request.meta['item']['img_name']
                return img_name

            #将item传递给下一个即将被执行的管道类
            def item_completed(self,request,item,info):
                return item

CrawlSpider全站数据爬取

- CrawlSpider就是Spider的一个子类
    - 如何创建一个基于CrawlSpider的一个爬虫文件
        - scrapy genspider -t crawl spiderName  www.xxx.com
- LinkExtractor(Allow="正则表达式"):链接提取器
        - 作用:根据指定的规则(Allow)进行url的提取
    - 注意:Allow=“”:可以将网站的所有链接都捕获到
- Rule(link,callback,fllow=True):规则解析器
        - 作用:
            - 1.接受到link提取到的链接,然后对其进行请求发送
            - 2.根据指定规则对请求到的数据进行数据解析
            - follow=True:将链接提取器 继续作用到 链接提取器 提取到的链接 对应的页面源码中

- 深度爬取:
    - 让手动请求scrapy.Request()发送,请求传参和LinkExtractor,Rule一起使用实现深度爬取

分布式

  • 概念:搭建一个分布式机群,共同执行一组代码,联合对同一个资源的数据进行分布且联合爬取

  • 实现方式:

  • 简称:scrapy+redis

  • 全称:Scrapy框架+scrapy-redis组件

    • 原生的scrapy框架是无法实现分布式
    • 原生scrapy中的调度器和管道无法被共享
  • scrapy-redis组件的作用

    • 可以给原生的scrapy框架提供可以被共享的管道和调度器
  • 环境的安装:

    • pip install scrapy-redis

    • 实现流程:

    • 修改爬虫文件中爬虫类对应的操作

      • 1.导包:from scrapy_redis.spiders import RedisCrawlSpider
      • 2.将爬虫类的父类修改成RedisCrawlSpider
      • 3.将start_url删除,添加一个redis_key=‘可以被共享调度器队列的名称’
      • 4.进行常规的请求和解析和向管道提交item的操作即可
    • 对settings.py配置

      • 配置管道:
        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 = 'redis服务的ip地址'

      • REDIS_PORT = 6379

      • redis的配置文件进行修改:redis.windows.conf

        • 56行:#bind 127.0.0.1
        • 75行:protected-mode no
      • 启动redis服务和客户端:

        • 携带配置文件启动redis服务:
          • redis-server.exe redis.windows.conf
        • 启动客户:
          • redis-cli
      • 启动程序:

        • 在终端中进入到爬虫文件对应的目录中:
          • scrapy runspider fbs.py
      • 向调度器的队列中仍入一个起始的url:

        • redis-cli:
          • lpush redis_key的属性值 www.xxx.com

增量式爬虫

  • 监测网站数据更新的情况。以便于爬取到最新更新出来的数据
    • 核心:去重
    • 记录表:
      • 特性:永久性存储 (redis中的set)作用:
      • 记录爬取过的数据对应的url可以以明文的形式存储(url数据长度较短)
      • 记录的数据对其生成一个【数据指纹】(url数据长度长)
      • 数据指纹就是该组数据的唯一标识
posted @ 2020-04-21 14:52  我的儿  阅读(310)  评论(0)    收藏  举报