Yao Lirong's Blog

Python网络爬虫与信息提取

2020/11/17
loading

Python网络爬虫与信息提取 - 嵩天

规则

requests.get()

r = requests.get(string url, timeout = n) r 是一个 response 类,它包含如下属性

  • r.status_code: 返回码
  • r.text: HTTP 相应内容的字符串形式
  • r.encoding: 从 header 中猜测的网页编码方式
  • r.apparent_encoding: 从内容中分析出来的编码方式
  • r.content:HTTP 相应内容的二进制形式

如果过 n 秒后还没有成功得到服务器的相应/从服务器读取数据,程序会 throw TimeOutError

requests 库的异常

在 request 网络连接时,我们一定要用 r.raise_for_status() 来在返回码不是200正常时,raise exception,然后用 try ... catch ... 语句保证异常被有效处理

  • requests.ConnectionError: 网络连接错误异常,如DNS查询失败、拒绝连接等
  • requests.HTTPError: HTTP错误异常
  • requests.URLRequired: URL缺失异常
  • requests.TooManyRedirects: 超过最大重定向次数,产生重定向异
  • requests.ConnectTimeout: 连接远程服务器超时异常
  • requests.Timeout: 请求URL超时,从整个发起URL请求开始产生超时异常

HTTP 协议对资源的操作

  • GET: 请求获取URL位置的资源
  • HEAD: 请求获取URL位置资源的响应消息报告,即获得该资源的头部信息,节省带宽
  • POST: 请求向URL位置的资源后附加新的数据,不改变现有内容,只是增加
  • PUT: 请求向URL位置存储一个资源,覆盖原URL位置的资源
  • PATCH: 请求局部更新URL位置的资源,即改写该处资源的部分内容 (与 PUT 相比节省带宽)
  • DELETE: 请求删除URL位置存储的资源

requests.request()

r = requests.request(string method, string url, **kwargs)

  • method 对应上文六种方式
  • Optional Arguments:
    • params=kv: 其中 kv 是一个 dict 值,在 url 后代入几个参数,形如 ?k1=v1&key2=v2&...
    • headers = hd: hd 是一个 dict 值,定制访问服务器时的 header 字段,有的时候网站会保护自己的数据不允许爬虫访问,我们改变 headers 以后就可以伪装成浏览器
    • data = dt, json = js: 在 postput时将 dtjs 传给服务器

Robots 协议

通过这个协议对网络爬虫能爬取的东西进行限制,一般就在网络的根目录url/robots.txt 下,一个例子是京东的协议

如果你的爬虫速度非常慢,访问次数很少,访问量不大(近似人类行为),原则上可以不遵守 robots 协议

提取

BeautifulSoup 的基本元素

from bs4 import BeautifulSoup

s = BeautifulSoup(r.text, "html.parser") returns the r.text restructured as html type.

  • s.tag: 标签,最基本的信息组成单元,对应 html 中的一对尖括号,下文中 t = s.tag
    • t.name: 标签的名字,比如 <p></p> 的名字是 p
    • t.attrs: 标签的属性,以字典形式存储。<p class="..." id="..."></p>
    • t.string: 两个尖括号间的字符内容,即<p>...</p> 中的 ...

使用 BeautifulSoup 遍历 HTML

h = s.head 返回 soup 中的第一个 head 标签

BautifulSoup 的每个类型都带有 pretify() 函数,优化打印效果: h.prettify(), s.prettify(), …

下行遍历

  • h.contents: 返回一个列表,包含此 tag 下的所有儿子节点
  • h.children: 返回一个所有儿子节点的 iterator, 即可以使用 for c in h.children: ... 遍历儿子节点
  • h.descendant: 返回所有子孙节点的 iterator

上行遍历

  • h.parent: 返回 h 的父亲标签
  • h.parents: 返回 h 的 ascendant 的 iterator,即一直到根节点的所有节点,用来循环遍历 ascendant

平行遍历

注意树中可能存在 NavigableString, Tag, Comment 种种类型,我们需要进行判断,不能假设平行遍历的下一个绝对是 tag

  • next_sibling: 返回按照HTML文本顺序的下一个平行节点标签
  • previous_sibling: 返回按照HTML文本顺序的上一个平行节点标签
  • next_siblings: 返回按照HTML文本顺序的后续所有平行节点标签的 iterator
  • previous_siblings: 返回按照HTML文本顺序的前续所有平行节点标签的 iterator

信息标记的形式

  1. XML (Extensible Markup Language): 通过标签表达所有信息;可扩展性好,但比较繁琐;用于互联网上的信息交互和表达

    用一对标签表达其中的信息<tag> ... </tag>,如果没有内容,则只用一个标签(即一对尖括号)来表达<tag/>.

  2. JSON (JavaScript Object Notation): 有类型的键值对;适合程序处理直接使用;用于云端和客户端的通信,一般在接口处使用

  3. YAML (YAML Ain’t Markup Language): 无类型的键值对;文本信息比例最高,可读性高;用于系统配置文件中

    • 使用缩进表达所属关系

      1
      2
      3
      name : 
      oldName: 延安自然科学院
      newName: 北京理工大学
    • - 表示并列关系

      1
      2
      3
      name: 
      -延安自然科学院
      -北京理工大学
    • | 表达整块数据(可以跨越多行),# 表达注释

      1
      2
      text: | #学校介绍
      北京理工大学天下第一bulabulabula

使用 BeautifulSoup 提取信息

我们使用find_all 来提取HTML页面中的信息:

s.find_all(name, attrs, recursive, string, ...) 返回一个存储着查询结果的列表

  • name: 对标签名称的检索字符串
    • 可以是一个字符串: 寻找所有 a 标签 s.find_all("a")
    • 也可以是一个包含字符串的列表:寻找所有 a 标签和 b 标签 s.find_all(["a","b"])
    • 可以是一个正则表达式:寻找所有名字中包含 b 的标签s.find_all(re.compile("b")) 会返回 b 标签和 body 标签
  • attrs: 对标签属性值的检索字符串
    • 查找包含某一属性的标签:s.find_all("p", "course") 返回所有带有 course 这个 attribute 的 p tag
    • 查找包含某一属性等于指定值的标签:s.find_all(id = "link1") 返回 [<a class="py1" href="http://www.icourse163.org/course/BIT-268001" id="link1">Basic Python</a>]
  • recursive: 表示是否对子孙全部检索的布尔值,默认True
  • string: 对标签中的字符串进行搜索:s.find_all(string = re.compile("python")) 返回所有包含有 python 的标签内容

简易表示方法:<tag>(..)等价于 <tag>.find_all(..); soup(..) 等价于 soup.find_all(..)

实战

如果我们不需要通过分析页面架构,只是通过在 html 文件中搜索就可以得到想要的信息的话,这无疑是最简单的。这种情况可能发生于信息存在键值对中,如此搜索变得很方便

  • raw string: r'[1-9]\d{5} 转义符 escape character 不转义,表达其在字符串中的原意
  • string: '[1-9]\\d{5}' 转义符 escape character 有

Re 库的基本使用

  • re.search(pattern, string): 在 string 中搜索匹配 pattern 的第一个位置,返回 match 类型
  • re.match(p, s): 从 s 的开始位置起匹配 p,返回 match 类型
  • re.findall(p, s) : 搜索 s ,以列表类型返回全部与 p 匹配的子串
  • re.split(p, s, maxsplit=n): 将 p 按照 s 匹配结果进行分割,将匹配的部分去掉,其他部分分割成子串存在列表中;可选参数 maxsplit 最多分为 n 个部分
  • re.sub(p, repl, s, count=n): 将 n 个 s 中与 p 匹配的子串替换成 repl,n如果未定义则默认替换所有

针对对于同一正则表达式的多次操作,我们可以将一个正则表达式编译为一个 pattern 类型,之后就可以多次使用针对这个类型的以上函数操作 regex = re.compile(pattern) regex.search(s), regex.match(s), ...

Re 库的 match 对象

match 对象就是一次匹配的结果,包含许多关于这次匹配的信息

  • .string: 待匹配的文本
  • .re: 匹配时使用的patter对象(正则表达式)
  • .pos: 正则表达式搜索文本的开始位置 (即在 string 中搜索的范围)
  • .endpos: 正则表达式搜索文本的结束位置
  • .group(0): 获得匹配后的字符串
  • .start(): 匹配结果在原始字符串的开始位置
  • .end(): 匹配结果在原始字符串的结束位置
  • .span(): 返回(.start(), .end())

其他技巧

中文的打印与对齐

我们一般使用 string.format() 函数格式我们的输出,用法:theFormat.format(parameter1, parameter2, ...)

formatter string 中有几个参数:(注意必须严格遵守这个顺序

  • {n} 表示使用第 n 个参数填充这个部分,不指定则按照与参数的对应顺序来
  • {:p} 使用参数 p 填充,不指定则默认为空格
  • {:<n} {:^n} {:>n} 左对齐,居中对齐,右对齐,宽度为 n
  • {:,} 在千位用逗号分隔数字
  • {:.n} 浮点数保留小数点后 n 位
  • {:<type>} 可以是 d 代表整形,f 代表浮点型,e 代表科学计数法浮点型,% 代表百分号浮点型

"{0:^10}\t{1:{2}^10}".format("排名","学校名称",chr(12288)) 打印 第零个参数(居中对齐宽度为10),打印 tab,打印第一个参数(用第2个参数进行居中填充宽度为10) 第二个参数是中文

"{1:x<20,.3f}".format(0,21031295.21413) 打印 ‘21,031,295.214xxxxxx’

在 UTF-8 编码中,对应的中文空格字符是 chr(12288).

CATALOG
  1. 1. 规则
    1. 1.1. requests.get()
    2. 1.2. requests 库的异常
    3. 1.3. HTTP 协议对资源的操作
    4. 1.4. requests.request()
    5. 1.5. Robots 协议
  2. 2. 提取
    1. 2.1. BeautifulSoup 的基本元素
    2. 2.2. 使用 BeautifulSoup 遍历 HTML
      1. 2.2.1. 下行遍历
      2. 2.2.2. 上行遍历
      3. 2.2.3. 平行遍历
    3. 2.3. 信息标记的形式
    4. 2.4. 使用 BeautifulSoup 提取信息
  3. 3. 实战
    1. 3.1. Re 库的基本使用
    2. 3.2. Re 库的 match 对象
  4. 4. 其他技巧
    1. 4.1. 中文的打印与对齐