[TOC]

Crawler

1. 准备工作 查看对应的url.

发送请求路径后,返回的是一个html网页得源代码,将信息从网页源代码中提取出来.

对于网页资源得请求是持续得,而不是一下子就能够返回整个网页得.

使用谷歌浏览器,在控制台界面选择NetWork,选择时间轴上的信息,然后点击Name中选定的内容,出现的Headers就是我们发送给服务器的内容.具体服务器发送给本地的内容存在于Headers旁边的Response模块.

P1

其中的Headers中的内容,拉到最后,有一个User-Agent指代用户代理,也就是浏览器版本内容等等.

2. 层级结构

根据控制台界面上特定元素的定位信息,来确定要爬取内容的层级结构.

P2

3. 编码规范

1
# coding=utf-8

Python文件中可以加入main函数用于测试程序:

用来定义程序的入口地址或者说程序开始运行的第一个函数位置.相对更加清楚地看到程序地流程.

1
2
if __name__ == "__main__":
# 巴拉巴拉

4. 整体流程

  1. 爬取网页

  2. 解析数据

    数据解析通常是逐一解析数据,与爬取网页通常是共同存在的,同步地

  3. 保存数据

5. 爬虫伪装

发送请求时对对象封装.

通常选择将发送Request对象中的headers设置为浏览器的样式.

6. 函数详解

关于用到的库

整体库

1
2
3
4
5
from bs4 import BeautifulSoup # 网页解析获取数据
import re # 正则表达式,文字匹配
import urllib.request,urllib.error # 指定url,获取网络数据
import xlwt # 进行excel操作
import sqlite3 # 进行SQLite 数据库操作

urllib

urllib的作用是向另一端的服务器发送一个request,然后得到一个responce,这个收到的响应通常等价于上面提到的控制台中的Responce内容.

bs4

re正则表达式

字符串模式.

建议在正则表达式中,被比较的字符串前面加上r 不用担心转义字符

常用参考传送门

P4

P5


Re库主要功能:

P6

P7

常用功能:

re.compile(pattern)

预加载,相当于先编译一下匹配的pattern.

那么在后续的函数使用时,是使用该函数返回的obj进行match.

re.match()

通常只返回找到的第一个符合条件的位置,并且是从头开始匹配

re.findall()

返回所有符合条件的内容,返回形式为列表

re.sub()

将给定的字符串中符合表达式的目标内容替换成给定的内容

1
2
3
# sub
# 将前面的换成后面的
print(re.sub('a', 'A', 'What are you talking?'))

re.finditer(pattern,string)

返回的是一个迭代器指针,通过for循环可以一次访问指针的内容,包括匹配结果字符串match和位置span

re.search(pattern,string)

返回的相当于是finditer函数返回的迭代器指针指向的内容,且是第一个.

这种东西称之为match对象,从match对象中拿数据,需要使用.group()方法,但返回的可能不是我们想要的,而是存在冗余信息.那么如果获取我们想要的特定内容,可以在想要获取的内容前面增加以下代码:

1
(?P<varName>.*?)

这样,匹配的内容将会被放在varName中,然后在使用时,可以通过.group("varName")进行获取.

例子:image-20220224235237631

re.S等状态位

可以在编译也就是预加载的时候进行处理,用来防止换行的出现

XPATH

是在XML文档中搜索内容的一门语言,相对简单,爬虫多用.

节点关系与树形结构类似.含有父结点,子结点,同胞节点的概念.

采用类文件夹路径的方式进行查找.

pip install lxml

进行内容获取时,通过调用其中的etree

例如:

1
2
3
4
from lxml import etree

xml=""
tree=etree.XML(xml[,parser,base_url])

其中除了可以应用于XML之外,还可以

层级结构:

1
result=tree.xpath("/book/author/nick/text()")

通过text()进行内容的提取,只完全根据路径信息进行提取,返回的是一个列表list.

如果想要获取某个层级下的所有的后代节点,通过

1
result=tree.xpath("/book/author//nick/text()")

结果是找到author下的所有后代nick节点

如果想要进行层级的跳跃,例如我不关心儿子节点是什么,我只关心孙子节点,那么使用通配符*

举例:

1
result=tree.xpath("/book/author/*/nick/text()")

如果我只想要获取到的若干中间节点x中的第n个**(从1开始计数)**,那么可以写作:

1
result=tree.xpath("/a/b/.../x[n]/...")

如果想要获取到中间节点x的某个属性值attrvalue的中间节点.那么可以写作:

1
result=tree.xpath("/a/b/.../x[@attr='value']/...")

想要在对获得到的所有标签进行遍历时,进一步进行相关搜索,可以通过:

1
2
3
result=tree.xpath("/html/.../")
for per_line in result:
per_line.xpath("./li/...") # 关键点就在于.

获取拿到的标签的属性值:

1
per_line.xpath("./a/@href") # 获取到a标签中的href值

综合来说

小技巧:

可以通过抓包工具中获取一个标签的XPATH路径,然后进行微调即可获取同级的内容.

requests

进阶使用

1. cookie处理

可以通过session进行请求.(会话)

session的特点是,可以发送一连串的请求且这一连串的请求中的cookie不会丢失.

因此我们的步骤是:

  1. 登录->得到cookie
  2. 带着cookie请求后续内容

需要首先创建一个session对象,该对象具有getpost方法

举例:

1
2
3
4
5
6
7
8
9
10
11
# 首先创建一个session对象
session=requests.session
loginData={
"":"",
"":""
}
loginURL="..."
session.post(loginURL,data=loginData)
targetURL=""
# 此时得到的resp就是携带了cookie得到的相应内容
resp=session.post(targetURL)

2. 防盗链处理

这里的防盗链的处理以梨视频为例进行分析.

所谓的防盗链只是在请求某个页面时,header中需要存在一个项即Referer,也就是对面的服务器需要知道当前的请求来源,是从哪个页面发出的此请求.

基本步骤

  1. 首先进入梨视频主页,随点点击进入一个页面,上方即视频播放页.

  2. 通过浏览器抓包工具,查看接收到的XHR类型的文件

  3. 可以发现接收到了JSON数据包,其中包含的即视频的信息.

    image-20220318131012430

    通过其中的srcURL信息,尝试直接访问视频源,发现网页返回404错误代码.

  4. 比对网页中嵌入的视频源链接与我们通过JSON包拿到视频源链接,可以发现不同之处

    image-20220318131405875

    可以发现后者的视频源中,将前者的164...856部分的数据更换成了cont-1755372

    因此,只要我们尝试将两者进行转换,即可拿到视频.

  5. 对比视频原始所在的页面url,与可播放的url,可以知道,上述更换是将原始界面中的链接部分与后者相结合了.进而我们可以确定爬取思路.

    image-20220318132008708

  6. 爬取思路

    • 首先根据原url,发送请求抓取json数据包,提取出其中的systemTimesrcUrl,

    • srcUrl部分中的system替换为cont-原url后半部分.

    • 向得到的新URL发送请求,拿到视频数据.

  7. 代码

    其他相关程序:

    通过此程序,返回请求时的文件头,Get选项决定是否添加Referer项内容

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    # -*- codeing = utf-8 -*-
    # @Time : 2022/3/18 10:00
    # @Author : Baxkiller
    # @File : userAgent.py
    # @Software : PyCharm

    def GetHeaders(URL = "", Get = True):
    if not Get:
    headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36 Edg/98.0.1108.56',
    }
    return headers
    else:
    headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36 Edg/98.0.1108.56',
    'Referer': URL
    }
    return headers

    主爬虫程序:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    # -*- codeing = utf-8 -*-
    # @Time : 2022/3/18 12:51
    # @Author : Baxkiller
    # @File : LiVideo.py
    # @Software : PyCharm


    import requests
    from userAgent import GetHeaders
    from lxml import etree
    import time


    # 梨视频的视频爬取
    # 获取用户给定的视频链接
    def Get_InitURL():
    initURL = input("请输入想要下载的梨视频页面地址")
    backDigit = eval(initURL.split('_')[-1])
    return [initURL, backDigit]


    # 获取含有视频源的json字典文件,同时解析视频名称
    def getJson(videoID, initUrl):
    url = f"https://www.pearvideo.com/videoStatus.jsp"
    pars = {
    "contId": videoID,
    "mrd": "0.5174454474260293"
    }

    # 获取视频的名称
    name_resp = requests.get(url = initUrl, headers = GetHeaders(Get = False))
    tree = etree.HTML(name_resp.text)
    name = tree.xpath(".//h1[@class='video-tt']/text()")[0]
    if len(name) == 0:
    print("Error:Can't get the name of the Video")
    exit()
    else:
    print("The Video Name is :", name)
    # 防止封锁IP
    time.sleep(2)

    # 获取JSON内容
    resp = requests.get(url = url, headers = GetHeaders(initUrl), params = pars)
    json = resp.json()
    json['name']=name
    return json
    # 拿到通过json抓取到的视频源地址


    # 对拿到的json进行处理,拿到视频源
    def get_systime_SrcUrl(json):
    systemTime = json['systemTime']
    srcUrl = json['videoInfo']['videos']['srcUrl']
    return [systemTime, srcUrl]


    # 获取视频内容
    def askVideo(url):
    resp = requests.get(url)
    return resp.content


    # 将视频内容进行保存
    def SaveVideo(Video, path):
    try:
    f = open(path, mode = "wb")
    except Exception:
    lt = list('\\/:*?"<>|')
    for c in lt:
    path=path.replace(c,"_")
    f=open(path,mode = "wb")
    f.write(Video)
    f.close()

    if __name__ == "__main__":
    [url, subDigit] = Get_InitURL()
    json= getJson(subDigit, url)
    systime, videoSrc = get_systime_SrcUrl(json)
    url = videoSrc.replace(systime, f"cont-{subDigit}")
    print("Asking the URL ... ")
    VideoContent = askVideo(url)
    SaveVideo(VideoContent, f"{json['name']}.mp4")
    print("Video Save Finished!")

3. 代理(IP)

通过第三方的服务器去发送请求.

如果对于某个网站的请求发送非常频繁,为了防止IP被封锁,或者说为了解决被封锁掉的IP,可以通过代理进行访问.

一般来说,匿名度为透明的速度较快,而匿名度为高匿的比较慢.

代理的使用只需要在request的方法中添加参数proxies.

proxies本身的定义和赋值要区分httphttps.

举例:

1
2
3
4
5
6
7
8
9
import requests

proxies = {
"https": "https://218.60.8.83:3129"
}

url = "https://www.baidu.com"
resp = requests.get(url = url, proxies = proxies)
print(resp.text)

应当注意其中的第四行的两个https与第7行的https应该是对应的.

也就是httpsvshttps

相关知识

web请求解析全过程

  1. 服务器渲染

    在服务器方将数据与html整合,统一返回给浏览器.

  2. 客户端渲染(浏览器方)

    如果显示时含有某些内容,而在html的源代码中并没有直接出现该内容,属于客户端渲染.

    image-20220207171859471

    在浏览器方,将html骨架与数据结合在一起.

    第一次请求只需要一个html股价,然后其中包含脚本,再次向服务器段请求数据.

    第二次的请求可以得到数据,然后将数据与脚本结合,展示出来.

    以豆瓣电影中的喜剧片排行榜为例:

    第一次请求得到的html框架如下所示:

    image-20220207173033659

    第二次请求得到的数据,通过json文件的形式返回:

    image-20220207173157863

    第一次请求得到的骨架并不是我们所期望得到的.

http协议

全称超文本传输协议.

http协议的请求和响应都是分为三大块.

请求:

请求行-> 请求方式,请求url地址,协议.

请求头-> 放一些服务器要使用的附加信息

请求体-> 放一些请求参数.

响应:

状态行-> 协议,状态码

响应头-> 放一些客户端要使用的一些附加信息.(密钥/cookie)

响应体-> 服务器返回的真正客户端要用的内容.(HTML,JSON等)

请求头中常见的重要内容:

  1. user-agent:请求载体的身份信息.
  2. referer: 防盗链(这次请求是从哪个页面来的.用于反爬虫).
  3. cookie:本地字符串数据信息(用户登录信息,反爬的token)

响应头中的重要信息:

  1. cookie: 本地字符串数据信息(用户登录信息,反爬虫的token)
  2. 各种莫名其妙的字符串(一般都是token字样,防止各种攻击和反爬)

请求方式:

实战举例

爬取豆瓣某分类的榜单

  1. 首先从抓包工具中查找是否具有相关的内容,确定渲染类型.

    根据下图可以发现,该榜单内容属于浏览器端渲染.因此可以直接截取到相关的json文件.

    image-20220220192119486

  2. 其次,根据上图,查看对应文件的标头,得知请求方法.根据下图来看,可以知道是GET类型.因此可以直接通过向对应链接使用request.get(url=url,headers=headers)

    image-20220220192431545

  3. 查看是否存在参数的传递

    查看对应的Payload,可以发现这里存在着参数传递.

    image-20220220192711638
  4. 综合

    综上来看,确定要获取内容形式如下:

    https://movie.douban.com/j/chart/top_list作为基础链接,问号后的内容实际上是参数的传递.因此可以确定我们要使用的函数为:

    request.get(url=baseurl,param=para,headers=headers)

    其中的para以及headers为参数字典.

    以上图为例,其参数字典如下:

    1
    2
    3
    4
    5
    6
    7
    para = {
    "type": "11",
    "interval_id": "100:90",
    "action": "",
    "start": 0,
    "limit": 20,
    }
  5. 结果

    发送请求后得到的结果,通过resp.json()直接提取返回的内容值.提取到的是字典类型的数据dict.

    这里将请求结果存储在文件中,但是在存储之前要注意将原本的返回结果中的单引号'更换为json文件常使用的双引号"

  6. 代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    import requests


    def GetRank():
    url = "https://movie.douban.com/j/chart/top_list" # 原url链接问号后的内容为参数
    para = {
    "type": "11",
    "interval_id": "100:90",
    "action": "",
    "start": 0,
    "limit": 20,
    }
    headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36 Edg/98.0.1108.56',
    }
    # 通过url与para的结合 可以生成我们想要的url
    resp = requests.get(url = url, params = para, headers = headers)
    resp.close()
    return resp.json()


    if __name__ == "__main__":
    fhandle = open("rangeOfPlot.json", mode = "w", encoding = 'utf-8')
    res = Douban.GetRank()
    strRes = str(res)
    strRes = strRes.replace(__old = '\'', __new = '\"')
    fhandle.write(str(res))
    print("Finished!")

数据解析

适用条件:对于服务器渲染方式的网页,通过数据解析直接获取到网页的数据内容.

这里介绍三种方 式:RE(正则表达式),BS4(BeautifulSoup) ,XPATH(应用范围最广)

其他内容