通过HTTP的HEADER完成各种骚操作

作为一名专业的切图工程师,我从来不care网页的header,最多关心Status Code是不是200。但是HEADER真的很重要啊,客户端从服务器端获取内容,首先就是通过HEADER进行各种沟通!HEADER可以帮助我们完成许多骚操作,提高网站的性能,用户的体验。好了让我们来feel一下。

目录:

  • 网络爬虫(又被称为网页蜘蛛,网络机器人,在FOAF社区中间,更经常的称为网页追逐者),是一种按照一定的规则,自动地抓取万维网信息的程序或者脚本。另外一些不常使用的名字还有蚂蚁、自动索引、模拟程序或者蠕虫
  • Python学习网络爬虫主要分3个大的版块:明确目标,抓取,分析,存储
  • 明确目标 (要知道你准备在哪个范围或者网站去搜索)
  • 爬 (将所有的网站的内容全部爬下来)
  • 取 (去掉对我们没用处的数据)
  • 处理数据(按照我们想要的方式存储和使用)
    网络爬虫要做的,简单来说,就是实现浏览器的功能。通过指定url,直接返回给用户所需要的数据,而不需要一步步人工去操纵浏览器获取。

推荐一篇文章:关于反爬虫,看这一篇就够了

初级骚操作

  • 多语言(Accept-Language
  • 防盗链(RefererReferered
  • gzip,简单地说就是省流量(Accept-EncodingContent-Encoding

1.通用爬虫 VS 聚焦爬虫

多语言

多语言就是一个网站可以实现多种语言的切换,这里不讨论建N个网站,一个网站也个语言。这里讨论如何智能返回用户所需的语言。

server client
向server扔过去了Accept-Language
接收对方的Accept-Language
字段大概这样子zh,en-US;q=0.9,en;q=0.8
开始处理,将字段变成带权重q的数组
排序好大概长这样[{"name":"zh","q":1},{"name":"en-US","q":0.9},{"name":"en","q":0.8}]
根据权重返回拥有的语言,有zh返回zh,没有zh就返回en-US
万一我没有对方需要的语言包,怎么办?急,在线等!
没办法了,只能给对方我们的官方(默认)语言
发送,请接收
您的ACCEPT语言已匹配 这个网站挺上道的,虽然是国外网站,但知道我是中文
我们没有你所在地区的语言包 emmmm,这是火星文吗?

附赠多语言的简易实现版:

let languages = {
    zh:{
        title:"你好",
        content:"同学"
    },
    en:{
        title:"Hey",
        content:"guy"
    },
}
//设置默认语言,万一用户的语言我们不支持呢?
let defaultLanguage="zh"
let http = require('http');
function getLanguage(client_langs){
    let finalLanguage=defaultLanguage
    try{
        if(client_langs){
            //排序获取语言顺序
            client_langs=client_langs.split(',').map(l=>{
                let [name,q] = l.split(';');
                q = q?Number(q.split('=')[1]):1 
                return {name,q}
            }).sort((a,b)=>b.q-a.q);
            //匹配服务器有的语言,并返回
            for(let i = 0 ;i <languages.length;i++){
                let name= languages[i].name;
                if(languages[name]){
                    finalLanguage=name;
                    break;
                }
            }
        }
    }catch(e){}
    return languages[finalLanguage]
}
http.createServer(function (req,res) {
    //获取客户端的语言
    let client_langs = req.headers['Accept-Language'];
    let lan=getLanguage(client_langs)
    //将语言打印到客户端
    res.end(`<p>${lan.title}</p><p>${lan.content}</p>`)
}).listen(3000);

1.通用爬虫:搜索引擎使用的爬虫系统

  1. 目标:尽可能把互联网上所有网页下载来,才能在本地服务器上,形成备份
  2. 实施:将网页以快照的形式保存在服务器上,进行关键字提取和垃圾数据剔除,提供用户一个访问的方式
    3.操作:爬取网页->存储数据->内容处理->提供检索
    4.搜索引擎排名——PageRank值——根据网站的流量进行顺序排名

防盗链

这个技术用的最多的应该就是对于图片的限制,只有本域名可以获取到,其他域名想都不要想。

server client
在某网站上请求了一张图片
通过RefererReferered发现此网站域名不在我方白名单内
此图片不提供给某网站
此时po上了一张万用土
支持正版请上我们网站

实现原理,此处我用iframe来做例子,其实原理很简单就是对比来源,要么和请求资源一致要么在白名单内,不然就拒绝。当然如果没有来源的情况下就直接放行,万一人家是单独打开的呢,不是盗链:

let http =  require('http');
let fs = require('fs');
let url = require('url');
let path = require('path');
// 设置白名单
let whiteList = ['localhost:3000'];
http.createServer(function (req,res) {
    //获取请求地址
    let { pathname } = url.parse(req.url);
    // 获取物理地址
    let realPath = path.join(__dirname,pathname);
    // 获取文件状态
    fs.stat(realPath,function(err,statObj) {
        if(err){
            res.statusCode = 404;
            res.end();
        }else{
             // 重点来了
            let Referer = req.headers['Referer'] || req.headers['referred'];
            //如果有来源
            if(Referer){
                //获取双方域名
                let current = req.headers['host'] 
                Referer = url.parse(Referer).host
                console.log(current,Referer)
                //如果域名相同活在白名单中,放行!
                if (current === Referer || whiteList.includes(Referer)){
                    fs.createReadStream(realPath).pipe(res);
                }else{
                    //不放行,此乃盗链!给你个眼神自行体会
                    fs.createReadStream(path.join(__dirname,'files/2.html')).pipe(res);
                }
            }else{
                //没有来源,也放行。万一是单独打开的呢~
                fs.createReadStream(realPath).pipe(res);
            }
        }
    })
}).listen(3000);

1.1. 爬取流程

1.选择已有的url地址,将url地址添加到爬取队列
2.从提取url,DNS解析主机IP,将目标主机IP添加到爬取队列
3.分析网页内容,提取链接,继续执行上一步操作

gzip

现代浏览器很高级,已经可以接受压缩包了。佩服佩服。那么该如何传输压缩的网页呢?

server client
向server扔过去了Accept-Encoding
大概结构是这样的gzip, deflate, br
get到了对方的用意,开始配置压缩
如果支持压缩,先设置个头部Content-Encoding
有很多种压缩方式,按照server优先支持的匹配
在线压缩网页,成功后返回client
欢欢喜喜省了流量,而且不影响体验

附赠建议代码,大家测试的时候,别忘了创建测试的html文件

let http = require('http');
//用于压缩文件所需的库
let fs = require('fs');
let path = require('path');
//压缩的库
let zlib = require('zlib');
http.createServer(function (req,res) {
    //获取客户端接受的压缩方式
    let rule = req.headers['Accept-Encoding'];
    // 创建原文件可读流
    let originStream=fs.createReadStream(path.join(__dirname, '1.html'));
    if(rule){
        // 啊啊啊!正则是个坎,我怕我是跨不过去了。
        if(rule.match(/bgzipb/)){
            //如果支持压缩!一定要设置头部!
            res.setHeader('Content-Encoding','gzip');
            originStream=originStream.pipe(zlib.createGzip())
        } else if (rule.match(/bdeflateb/)){
            res.setHeader('Content-Encoding', 'deflate');
            originStream=originStream.pipe(zlib.createDeflate())
        }
    }
    // 输出处理后的可读流
    originStream.pipe(res)
}).listen(3000);

1.2.搜索引擎获取新网站URL地址

1.主动推送URL地址->提交URL地址给搜索引擎->百度站长平台
2.其他网站的外链
3.搜索引擎和DNS服务商共同处理,收录新的网站信息

中级操作

初级操作大多只需要靠配置HEADER即可以实现,中级我们当然要难一点,大多需要client和server打配合。

  • client给server发送内容(Content-TypeContent-Length)
  • client从server获取内容(RangeContent-Range)
  • client爬虫,抓取网页

1.3.通用爬虫限制:Robots协议【约定协议robots.txt】

  • robots协议:协议指明通用爬虫可以爬取网页的权限
  • robots协议是一种约定,一般是大型公司的程序或者搜索引擎等遵守

client给server发送内容

server client
给你了一串数据,你给处理下
没头没脑,谁知道你要做什么,请设置好HEADER
好吧,告诉你Content-TypeContent-Length
可以可以,数据的内容类型是长度是很必要的
把数据传给你了,你看一下
收到~监听收到的数据是一组Buffer
接受完毕,合并Buffer
根据Content-Type对数据进行处理
格式化数据,end

Server代码

let http = require('http');
let server = http.createServer();
let arr=[]
server.on('request', (req, res)=>{
  req.on('data',function (data) {
    //把获取到的Buffer数据都放入熟组
    arr.push(data);
  });
  req.on('end',function() {
    // 请求结束了,好了可以开始处理断断续续收到的Buffer了
    // 合并buffer
    let r = Buffer.concat(arr).toString();
    if (req.headers['content-type'] === 'x-www-form-urlencoded'){
        let querystring = require('querystring');
        r = querystring.parse(r); // a=1&b=2然后格式化
        console.log("querystring",r);
      } else if (req.headers['content-type'] === 'application/json'){
        //听说是JSON格式的
        console.log("json",JSON.parse(r));
      } else{
        //没有格式?那原来是啥就是啥吧。
        console.log("no type",r);
      }
      arr=[]
      res.end('结束了!');
  });
})
server.listen(3000,()=>{
  console.log(`server start`);
});

Client代码

// 设置请求地址的配置
let opts = {
  host:'localhost',
  port:3000,
  path:'/',
  // 头部设置很重要,头部设置很重要,头部设置很重要
  headers:{
    'Content-Type':'x-www-form-urlencoded',
    //长度超过3就没有人理你了
    "Content-Length":7
  }
}
let http = require('http');
let client = http.request(opts,function (res) {
  res.on('data',function (data) {
      console.log(data);
  })
});
client.end("a=1&b=2");

1.4. 缺陷:

  • 只能爬取和文本相关的数据,不能提供多媒体(图片、音乐、视频)以及其他二进制文件(代码、脚本等)的数据爬取
  • 提供的结果千篇一律,提供给所有人通用的一个结果,不能根据具体的人的类型进行区分

client从server获取部分内容

server client
我想要资源的部分内容
可以啊,告诉我范围
我放在HEADER中的Range了,bytes=0-3
Content-Range:bytes 0-3/7,请接受,此文件一共8字节,前3字节已经给你了 好的,那么把接下来的给我吧,bytes=4-7
给你给你都给你 end

大家都发现了吧,这样的range获取数据,完全是断点续传的简陋版啊!不过这边有一个点容易犯错就是文件大小的计算,因为文件字节的位置是按照0开始算,所以range的全范围都是0~size-1/size-1,大家注意下。

server 端

let http = require('http');
let fs = require('fs');
let path = require('path');
// 当前要下载的文件的大小
let size = fs.statSync(path.join(__dirname, 'my.txt')).size;
let server = http.createServer(function (req, res) {
  let range = req.headers['range']; //获取client请求访问的部分内容
  if (range) {
    let [, start, end] = range.match(/(d*)-(d*)/);
    start = start ? Number(start) : 0;
    end = end ? Number(end) : size - 1; // 10个字节 size 10  (0-9)
    console.log(`bytes ${start}-${end}/${size - 1}`)
    res.setHeader('Content-Range', `bytes ${start}-${end}/${size - 1}`);
    fs.createReadStream(path.join(__dirname, 'my.txt'), { start, end }).pipe(res);
  } else {
    // 会把文件的内容写给客户端
    fs.createReadStream(path.join(__dirname, 'my.txt')).pipe(res);
  }
});
server.listen(3000);

client端

let opts = {
    host:'localhost',
    port:3000,
    headers:{}
  }
let http = require('http');
let start = 0;
let fs = require('fs');
function download() {
    //分流下载,部分下载
    opts.headers.Range = `bytes=${start}-${start+3}`;
    start+=4;
    let client = http.request(opts,function (res) {
        let total = res.headers['content-range'].split('/')[1];
        res.on('data',function (data) {
          fs.appendFileSync('./download.txt',data);
        });
        res.on('end',function () {
            //结束之后,1s之后再下载
          setTimeout(() => {
              console.log(start,total)
            if (start <= total)
              download();
          }, 1000);
        })
    });
    client.end();
}
download()

2. 聚焦爬虫:

为了解决通用爬虫的缺陷,开发人员针对特定用户而开发的数据采集程序
特点:面向需求,需求驱动开发

client抓取网页内容,简易爬虫

这一块的操作其实很简单,只要建一个请求获取到网页就可以了。
难点在于:如何将游游有用信息剥离网页,过滤掉无用信息。
我这里抓去了百度的娱乐版,百度还算良心,是utf8的,不然就要乱码了。

let http = require('http');
let opts = {
  host:'news.baidu.com',
  path:'/ent'
}
//创建一个请求,获取网站内容
let client = http.request(opts,function (r) {
    let arr= [];
    //资源不可能一次下载完成,因此每次获取到数据都要push到arr中
    r.on('data',function (data) {
        arr.push(data);
    });
    r.on('end',function() {
        //合并资源
        let result = Buffer.concat(arr).toString();
        //对资源进行处理,可以是变成我这样的对象,之后不管做什么处理都很方便
        let content = result.match(/<ul class="ulist mix-ulist">(?:[sS]*?)</ul>/img).toString().match(/<li>(?:[sS]*?)</li>/img);
        content=content.map((c)=>{
            let href=/<a href="(?:[S]*?)"/img.exec(c)
            let title=/">(?:[sS]*?)</a>/img.exec(c)
            return {
                href:href[0].replace(/"/img,"").replace("<a href=",""),
                title:title[0].replace(/">/img,"").replace("</a>","")
            }
        })
        console.log(JSON.stringify(content))
        arr= [];
    })
});
client.end();

2.HTTP & HTTPS

  • HTTP:超文本传输协议:Hyper Text Transfer Protocal

  • HTTPS: Secure Hypertext Transfer Protocol 安全的超文本传输协议

  • HTTP请求:网络上的网页访问,一般使用的都是超文本传输协议,用于传输各种数据进行数据访问,从浏览器发起的每次URL地址的访问都称为请求,获取数据的过程称为响应数据

  • 抓包工具:在访问过程中,获取网络上传输的数据包的工具称为抓包工具,抓包:网络编程中专业术语名词,指代的是对网络上传输的数据进行抓取解析的过程。我之前用的是Wireshark,其他专业抓包工具如Sniffer,wireshark,WinNetCap.WinSock ,现在用的是Fiddler 抓包,Fiddler 下载地址

    • Fiddler 抓包简介
      1). 字段说明
      2). Statistics 请求的性能数据分析
      3). Inspectors 查看数据内容
      4). AutoResponder 允许拦截制定规则的请求
      5). Filters 请求过滤规则
      6). Timeline 请求响应时间
    • Fiddler 设置解密HTTPS的网络数据
    • Fiddler 抓取Iphone / Android数据包
    • Fiddler 内置命令与断点
  • 浏览器设置代理进行数据抓包——建议使用谷歌的插件快捷设置不同的代理——Falcon Proxy

3.urllib2

  • urllib2是python中进行网页数据抓取的一个操作模块,urllib2可以当作urllib的扩增,比较明显的优势是urllib2.urlopen可以接受Request对象作为参数,从而可以控制HTTP Request的headers,进而实现模拟浏览器、模拟登录等操作。
  • 在python3中,对urllib2进行了优化和完善,封装成了urllib.request进行处理。
  • Python 标准库 urllib2 的使用细节
  • urllib

编码函数:urlencode()
远程数据取回:urlretrieve()

  • urllib2:

urlopen()
Request()

urllib2第一弹——urlopen()

-urlopen()->response
->response->read()抓取网页数据
->response->info() 抓取网页请求报头信息
->response->geturl()抓取访问地址
->response->getcode()抓取访问错误码

注解:

  • urllib2库里面的urlopen方法,传入一个URL,协议是HTTP协议,urlopen一般接受三个参数,urlopen(url, data, timeout)

    • 第一个参数url即为链接,
    • 第二个参数data是访问url时要传送的数据,
    • 第三个timeout是设置超时时间。
  • response对象有一个read方法,可以返回获取到的网页内容,即response.read()

  • urlopen参数可以传入一个request请求,它其实就是一个Request类的实例,构造时需要传入Url,Data等等的内容


代码操作一

# -*- coding:utf-8 -*-
#引入
import urllib2

response=urllib2.urlopen('https://www.baidu.com')
content=response.read()
print(content)

Paste_Image.png

1.headers的属性介绍

User-Agent : 有些服务器或 Proxy 会通过该值来判断是否是浏览器发出的请求
Content-Type : 在使用 REST 接口时,服务器会检查该值,用来确定 HTTP Body 中的内容该怎样解析。
application/xml : 在 XML RPC,如 RESTful/SOAP 调用时使用
application/json : 在 JSON RPC 调用时使用
application/x-www-form-urlencoded : 浏览器提交 Web 表单时使用
在使用服务器提供的 RESTful 或 SOAP 服务时, Content-Type 设置错误会导致服务器拒绝服务

注意:Sublime使用正则匹配替换^(.*):(.*)$ --> "1":"2",
在pycharm中则是^(.*):(.*)$ --> "$1":"$2",

  • 随机添加/修改User-Agent

可以通过调用Request.add_header() 添加/修改一个特定的header 也可以通过调用Request.get_header()来查看已有的header。

# urllib2_add_headers.py

import urllib2
import random

url = "http://www.itcast.cn"

ua_list = [
        "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36 OPR/26.0.1656.60",
        "Opera/8.0 (Windows NT 5.1; U; en)",
        "Mozilla/5.0 (Windows NT 5.1; U; en; rv:1.8.1) Gecko/20061208 Firefox/2.0.0 Opera 9.50",
        "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; en) Opera 9.50",
        "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:34.0) Gecko/20100101 Firefox/34.0",
        "Mozilla/5.0 (X11; U; Linux x86_64; zh-CN; rv:1.9.2.10) Gecko/20100922 Ubuntu/10.10 (maverick) Firefox/3.6.10",
        "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/534.57.2 (KHTML, like Gecko) Version/5.1.7 Safari/534.57.2 ",
        "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.71 Safari/537.36",
        "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.64 Safari/537.11",
        "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.133 Safari/534.16",
        "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.101 Safari/537.36",
        "Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko",
        "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.1132.11 TaoBrowser/2.0 Safari/536.11",
        "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.71 Safari/537.1 LBBROWSER",
        "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; LBBROWSER)",
        "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; QQDownload 732; .NET4.0C; .NET4.0E; LBBROWSER)",
        "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; QQBrowser/7.0.3698.400)",
        "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; QQDownload 732; .NET4.0C; .NET4.0E)",
        "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.84 Safari/535.11 SE 2.X MetaSr 1.0",
        "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; SV1; QQDownload 732; .NET4.0C; .NET4.0E; SE 2.X MetaSr 1.0)",
    ]

user_agent = random.choice(ua_list)

request = urllib2.Request(url)

#也可以通过调用Request.add_header() 添加/修改一个特定的header
request.add_header("User-Agent", user_agent)

# 第一个字母大写,后面的全部小写
request.get_header("User-agent")

response = urllib2.urlopen(req)

html = response.read()
print html

代码操作二,伪装浏览器访问

# -*- coding:utf-8 -*-
#引入
import urllib2
from urllib2 import Request

#伪装浏览器访问
my_header={'User-Agent':'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.108 Safari/537.36 2345Explorer/8.7.0.16013'}

request=Request('https://www.baidu.com',headers=my_header)
response=urllib2.urlopen(request)
content=response.read()
print(content)

伪装浏览器的头部可以从审查元素中获取

2.Referer (页面跳转处)

Referer:表明产生请求的网页来自于哪个URL,用户是从该 Referer页面访问到当前请求的页面。这个属性可以用来跟踪Web请求来自哪个页面,是从什么网站来的等。

有时候遇到下载某网站图片,需要对应的referer,否则无法下载图片,那是因为人家做了防盗链,原理就是根据referer去判断是否是本网站的地址,如果不是,则拒绝,如果是,就可以下载;

3.Accept-Encoding(文件编解码格式)

Accept-Encoding:指出浏览器可以接受的编码方式。编码方式不同于文件格式,它是为了压缩文件并加速文件传递速度。浏览器在接收到Web响应之后先解码,然后再检查文件格式,许多情形下这可以减少大量的下载时间。

举例:Accept-Encoding:gzip;q=1.0, identity; q=0.5, ;q=0

如果有多个Encoding同时匹配, 按照q值顺序排列,本例中按顺序支持 gzip, identity压缩编码,支持gzip的浏览器会返回经过gzip编码的HTML页面。 如果请求消息中没有设置这个域服务器假定客户端对各种内容编码都可以接受。

4.Accept-Language(语言种类)

Accept-Langeuage:指出浏览器可以接受的语言种类,如en或en-us指英语,zh或者zh-cn指中文,当服务器能够提供一种以上的语言版本时要用到。

5. Accept-Charset(字符编码)

Accept-Charset:指出浏览器可以接受的字符编码。

举例:Accept-Charset:iso-8859-1,gb2312,utf-8

  • ISO8859-1:通常叫做Latin-1。Latin-1包括了书写所有西方欧洲语言不可缺少的附加字符,英文浏览器的默认值是ISO-8859-1.
  • gb2312:标准简体中文字符集;
  • utf-8:UNICODE 的一种变长字符编码,可以解决多种语言文本显示问题,从而实现应用国际化和本地化。
    如果在请求消息中没有设置这个域,缺省是任何字符集都可以接受。

本文由金沙官网线上发布于Web前端,转载请注明出处:通过HTTP的HEADER完成各种骚操作

您可能还会对下面的文章感兴趣: