手把手带你构建一个分布式爬虫系统实战 学习笔记

手把手带你构建一个分布式爬虫系统实战-小象学院-学习笔记

手把手带你构建一个分布式爬虫系统实战-第二期-小象学院-杨真

《分布式爬虫实战》第二期

课程目录

01 静态网页爬虫:爬虫的基础技术.flv 2:40:54
02 登录及动态网页的抓取.flv 2:19:24
03 微博的抓取.flv 2:42:00
04 多线程与多进程的爬虫.flv 2:02:20
05 微博数据的存储:分布式数据库及应用.flv 2:15:07
06 多机并行的微博抓取:分布式系统设计.flv 2:12:20
07 应对反爬虫的策略.flv 2:25:33
08 分布式系统的高可用与高并发处理.flv 2:13:47
09 日志系统、以及基于Page Rank的顺序调整.flv 2:13:54
10 日志、守护线程以及验证码处理.flv 2:07:54
11 分布式数据库架构分析、优化及要点.flv 2:23:10
12 自动摘要及正文抽取.flv 1:57:54
13 网页分类与针对文本的机器学习应用.flv 2:17:14
14 信息检索、搜索引擎原理及应用.flv 2:31:18
15 Scrapy录播视频.flv 2:21:02
16 Scrapy进阶录播视频.flv 2:10:09
17 网页排重.flv 2:19:19

讲课开始时间:2017-06-08 19:51

01 静态网页爬虫:爬虫的基础技术

讲课开始时间:2017-06-08 19:51

03:36 开始讲课

大纲

  • Python 环境搭建及基础类
  • HTML 相关技术:HTML、CSS 及 JavaScript
  • HTTP 协议及 Python 的 DOM 树选择器
  • 宽度与深度抓取介绍及比较
  • 不重复抓取策略及 BloomFilter
  • 网站结构分析
  • 马蜂窝网页抓取案例

对这门课程的预期,机器学习、搜索引擎。学习态度很重要。.pi

基础环境 - Python

13:11

  • Python 2.7

  • pip,并设置 pip 源

    配置 pip conf,自动设置源

    1
    2
    3
    4
    5
    # 新建目录
    mkdir ~/.pip/

    # 添加 pip.conf 文件
    vim ~/.pip/pip.conf
    1
    2
    [global]
    index-url=https://pypi.tuna.tsinghua.edu.cn/simple

    每次安装指定 source

    1
    pip install -i https://pypi.tuna.tsinghua.edu.cn/simple lxml

建议开发环境 Mac 或者 Linux。

Anaconda 4.2.0

Python 2.7.12

17:12

  • 下载 Anaconda,很多比较难以安装的源都已经包含了

  • 仍然配置 pip 源,各个系统默认 pip.ini 位置不同,需要根据实际情况设置

    官网:https://anaconda.org

    下载主页: https://www.continuum.io/downloads

    1
    pyenv install anaconda2-4.2.0

Python 底层是 C 的,lxml 是基于 C 语言编写,需要 C 语言编译环境。

HTTP 协议

18:26

request 分为 header 和 body

response 404,401

还有加密相关的

TCP/IP 四层 与 OSI 七层

19:21

传输层:TCP、UDP 协议

应用层:HTTP、FTP

监听 https 请求

https = http + ssl

HTTP 协议

23:16

  • 应用层的协议
  • 无连接:每次连接只处理一个请求
  • 无状态:每次连接、传输都是独立的

无状态的特性就必须使用 cookie 来标明身份。

HTTP HEADER

25:21

REQUEST 部分的 HTTP HEADER

  • Accept: text/plain
  • Accept-Charset: utf-8
  • Accept-Encoding: gzip, deflate
  • Accept-Language: en-US
  • Connection: keep-alive (之前建立 socket 连接不要关闭)
  • Content-Length: 348
  • Content-Type: application/x-www-form-urlencoded
  • Date: Tue, 15 Nov 1994 08:12:31 GMT
  • Host: en.wikipedia.org:80
  • User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:12.0) Gecko/0100101 Firefox/1.0
  • Cookie: $Version=1; Skin=new;

30:10 什么是 cookie

client 通过 http 向 server 发送请求,server 返回 set-cookie 字段。

cookie 大小最大 4kb。

keep-alive

34:17

Keep-Alive 功能使客户端到服务器端的连接持续有效,当出现对服务器的后续请求时,Keep-Alive 功能避免了建立或者重新建立连接。

RESPONSE 的 HTTP HEADER

34:27

  • Accept-Patch: text/example;charset=utf-8
  • Cache-Control: max-age=3600
  • Content-Encoding: gzip
  • Last-Modified: Tue, 15 Nov 1994 12:45:26 GMT
  • Content-Language: da
  • Content-Length: 348
  • Etag: “737060c”
  • Expires: Thu, 01 Dec 1994 16:00:00 GMT
  • Location: http://www.w3.org/pub/WWW/People.html (重定向,需要跳转)
  • Set-Cookie: UserID=JohnDoe; Max-Age=3600; Version=1 (设置 Cookie)
  • Status: 200 OK (返回值状态码)

Location 跳转的意思。

很多地方有用,需要认证的时候,进行登录的时候,有很多301跳转。

HTTP 响应状态码

30:40

  • 2XX 成功
  • 3XX 跳转
  • 4XX 客户端错误
  • 5XX 服务器错误

401 错误 可能是爬虫被 block。

500 错误 大多数是服务器出问题了。

HTTP 响应状态码 300

43:42

  • 300 Multiple Choices 存在多个可用的资源,可处理或丢弃
  • 301 Moved Permanently 重定向
  • 302 Found 重定向
  • 304 Not Modified 请求的资源未更新,丢弃

一些 Python 库,例如 urllib2 已经对重定向做了处理,会自动跳转;动态网页处理的时候,也是自动跳转,所以不需要单独处理。

自动解析响应头的 Location 里的 url 地址并去请求。

urllib 给用户设置了很多钩子,例如 cookie 的钩子。

HTTP 响应状态码 400、500

47:24

  • 400 Bad Request 客户端请求有语法错误,不能被服务器所理解
  • 401 Unauthorized 请求未经授权,这个状态代码必须和 WWW-Authenticate 报头域一起使用
  • 403 Forbidden 服务器收到请求,但是拒绝提供服务
  • 404 Not Found 请求资源不存在,eg:输入了错误的 URL
  • 500 Internal Server Error 服务器发生不可预期的错误
  • 503 Server Unabailable 服务器当前不能处理客户端的请求,一段时间后可能恢复正常

错误处理

48:33

  • 400 Bad Request 检查请求的参数或者路径
  • 401 Unauthorized 如果需要授权的网页,尝试重新登录
  • 403 Forbidden
    • 如果是需要登录的网站,尝试重新登录
    • IP 被封,暂停爬取,并增加爬虫的等待时间,如果拨号网络,尝试重新联网更改 IP
  • 404 Not Found 直接丢弃
  • 5XX 服务器错误,直接丢弃,并计数,如果连续不成功,WARNING 并停止爬取

HTTP 请求方法

50:13

  • GET (GET 方法没有 body)(数据内容放在 url 里面)(请求数据的方法)
  • POST (POST 方法有 body 数据区域)

HTML 及 CSS

55:32

浏览器中不能看到 style=”display:none”,这个就是爬虫陷阱。

Chrome Dev Tools: Console 中执行

1
2
3
4
5
// 使用类名获取元素
document.getElementsByClassName('r')

// 使用 id 获取元素
document.getElementsById('')

HTML

1:10:17

HTML 是超文本标记语言,简单来说是可以认为是一种规范或者协议,浏览器根据 HTML 的语言规范来解析 HTML。

与爬虫相关的规范有以下:

这一类 tag a 标记了外链用来做抓取,而 tr、p 用来进行内容抽取

  • Tag:<a> <tr> <p>

id、class 用在 css 上的,能帮助我们定位元素,作用和 tr p 类似:

  • Class
  • Id

参考网站:w3school 在线教程

DOM 树

1:13:26

DOM 树最重要的作用是用来做网页数据分析及提取,我们可以充分利用 TAG、CLASS、ID 来找出某一类或某一个元素并提取内容。

JavaScript

1:17:29

JavaScript 就是运行在前端的编程语言,最典型的用在动态网页的数据、内容加载及呈现上,JavaScript 做网络请求的时候最常用的技术是 AJAX(Asynchronous JavaScript and XML),专门用来异步请求数据,这些数据是我们抓取的时候需要用到的。

1:21:44 休息

1:22:17 问题解答

  • 编码问题

  • jQuery 的 Python 库 pyquery

  • session 和 cookie 什么关系?

    session 是服务器端的,cookie 是客户端的。

  • http 的 keep-alive 是操作 tcp 连接吗?

    对,是 socket tcp 连接。

  • selenium 会讲

  • 经过混淆的页面怎么爬取?

  • 汽车之家论坛加密的 HTML 代码

宽度及深度抓取

1:30:30

横向爬取优先:BSF

深度爬取优先:DSF

爬虫的抓取对象类型

1:31:38

Web API 数据获取

1:37:01

Mafengwo 的结构

1:37:42

深度优先策略

1:38:02

宽度优先策略

1:38:22

选择哪种策略?

1:38:34

  • 重要的网页距离种子站点比较近
  • 万维网的深度并没有很深,一个网页有很多路径可以到达(最大深度有17层左右)(数据结构:树、图)
  • 宽度优先有利于多爬虫并行合作抓取 (队列)
  • 深度限制与宽度优先相结合 (要设置最大深度 max-depth)

爬取策略:以宽度优先的方式加上爬取深度的限制。

不重复抓取策略

1:46:41

网站每个子板块一般都有指向首页的链接,首页有子板块的链接,形成循环。

如何记录抓取历史

1:48:38

  1. 将访问过的 URL 保存到数据库。(效率太低)
  2. 用 HashSet 将访问过的 URL 保存起来。那只需接近 O(1) 的代价就可以查到一个 URL 是否被访问过了。(消耗内存)
  3. URL 经过 MD5 或 SHA-1 等单向哈希后再保存到 HashSet 或数据库。(标准 MD5 长度16个字节)
  4. Bit-Map 方法。建立一个 BitSet,将每个 URL 经过一个哈希函数映射到某一位。(Bit-Map 不是最佳方式)

老师向学员求助,有谁知道怎么退出 vnc

2:00:44

老师要崩溃了哈

MD5 函数

2:03:42

MD5 签名是一个哈希函数,可以将任意长度的数据量转换为一个固定长度的数字(通常是4个整形,128位)。计算机不可能有2的128次方那么大内存,因此实际的哈希表都会是 URL.MD5 再 %n (即取模)。现实世界的 URL 组合必然超越哈希表的槽位数,因此碰撞是一定存在的。一般的 HASH 函数,例如 Java 的 HashTable 是一个 HASH 表再跟上一个链表,链表里存的是碰撞结果。

两个网页的哈希值一样,就容易漏数据。

提高效率

2:09:11

  • 评估网站的网页数量
  • 选择合适的 HASH 算法和空间阀值,降低碰撞机率
  • 选择合适的存储结构和算法

使用搜索引擎评估网站的网页数量

使用 Google:site:www.mafengwo.cn/gonglve/

使用 百度:site:www.mafengwo.cn一般有显示网站的大致网页数量。

BITMAP 方式记录

2:14:13

将 URL 的 MD5 值再次哈希,用一个或多个 BIT 位来记录一个 URL:

  1. 确定空间大小 e.g. facebook 1.5Gb
  2. 按倍增加槽位 e.g. 16GB
  3. HASH 算法映射(murmurhash3,cityhash)Python:mmh3 bitarray

安装 murmurhash3 bitarray

1
2
3
# pip install murmurhash3  # murmurhash3 只支持 Python >= 3.2.0
pip install mmh3==2.4 # mmh3 支持 Python 2.7, Python 3.3 and higher.
pip install bitarray==0.8.1

示例

1
2
3
4
5
6
7
8
9
from bitarray import bitarray
import mmh3

offset = 2147483647 // 2^31 - 1
bit_array = bitarray(4*1024*1024*1024) # 空间大小 4个G
bit_array.setall(0)

b1 = mmh3.hash(url, 42) + offset # 42 是哈希的因子,一个数不能以负数开始所有加上 offset。
bit_array[b1] = 1

BITMAP 方式记录 压缩

2:19:21

  • 优势:对存储进行了进一步压缩,在 MD5 的基础上,可以从128位最多压缩到1位,一般情况,如果用 4bit 或者 8bit 表示一个 url,也能压缩32或者16倍。
  • 缺陷:碰撞概率增加。

Bloom Filter

2:20:37

Bloom Filter 使用了多个哈希函数,而不是一个,创建一个 m 位 BitSet,先将所有位初始化位0,然后选择 k 个不同的哈希函数。第 i 个哈希函数对字符串 str 哈希的结果记为 h(i,str),且 h(i,str)的范围是0到 m-1。只能插入,不能删除!!

BITMAP 为了降低碰撞概率,需要设置大量空的 bit 位,来保证它们不被碰撞。

Bloom Filter 算法通过多个哈希函数同时命中,才算碰撞。

pybloomfilter

2:27:03

安装

1
pip install pybloomfilter==1.0

使用源码安装

1
2
3
git clone https://github.com/axiak/pybloomfiltermmap.git
cd pybloomfiltermmap
python setup.py install

构造函数

Sample

1
2
3
4
5
6
7
8
fruit = pybloomfilter.BloomFilter(100000, 0.1, '/tmp/words.bloom')  # 0.1是错误率, words.bloom 是种子文件。
fruit.update(('apple', 'pear', 'orange', 'apple'))

len(fruit)

'mike' in fruit

'apple' in fruit

官方文档

https://media.readthedocs.org/pdf/pybloomfiltermmap3/latest/pybloomfiltermmap3.pdf

Welcome to Python BloomFilter’s documentation! — Python BloomFilter v0.3.2 documentation

如何有效记录抓取历史

2:29:00

  • 多数情况下不需要压缩,尤其网页数量少的情况
  • 网页数量大的情况下,使用 Bloom Filter 压缩
  • 重点是计算碰撞概率,并根据碰撞概率来确定存储空间的阀值
  • 分布式系统,将散列映射到多台主机的内存

未讲内容:网站结构分析

2:23:07

  • mafengwo.cn 网站分析
  • Robots.txt
  • Sitemap.xml

问题解答

2:34:00

课程调查表

滑动验证码无法破解,要做图像识别,但是这远远超过了爬虫的技术了。

新建PyCharm项目 chinahadoop_crawl_spider
新建PyCharm项目 chinahadoop_crawl_spider_py2.7

cd chinahadoop_crawl_spider

macOS下安装pipenv

1
brew install pipenv

添加pipenv自动补全

1
2
3
vim ~/.zshrc

eval "$(pipenv --completion)"

初始化pipenv

1
pipenv --python=/Users/jinlong/.pyenv/versions/2.7.14/bin/python
1
pipenv --python=/Users/jinlong/.pyenv/versions/3.6.3/bin/python

修改pypi源

1
2
3
4
5
#linux
sed -i "s#python.org#douban.com#g" Pipfile

#macOS 和linux不同,需要加“”
sed -i "" "s#python.org#douban.com#g" Pipfile

02 登录及动态网页的抓取

讲课开始时间:2017-06-13 19:58

03:02 开始讲课

大纲

  • 网站结构分析及案例:马蜂窝
  • XPath
  • 正则表达式
  • 动态网页
  • Headless 的浏览器:PhantomJS
  • 浏览器的驱动:Selenium

网站结构分析 Robots.txt

04:56

  • 网站对爬虫的限制
  • 利用 sitemap 来分析网站结构和估算目标网页的规模

Robots:www.mafengwo.cn/robots.txt

Sitemap:www.mafengwo.cn/sitemapIndex.xml

Sitemap

10:34

1
2
3
4
5
6
7
8
<sitemap>
<loc>http://www.mafengwo.cn/article-0.xml</loc>
<lastmod>2017-02-04</lastmod>
</sitemap>
<sitemap>
<loc>http://www.mafengwo.cn/article-1.xml</loc>
<lastmod>2017-02-04</lastmod>
</sitemap>

分析网站结构

10:59

抓特定区域的游记,网站有反爬措施策略。

403 401 501 典型的爬虫被禁止的返回状态码。

目的地旅游攻略 - 马蜂窝

有效率抓取特定内容

18:06

  • 利用 sitemap 里的信息

直接对目标网页 .html 进行抓取

  • 对网站目录结构进行分析

大多数网站都会存在明确的 top-down 的分类的目录结构,我们可以进入特定目录进行抓取。

对于 www.mafengwo.cn 这个网站,所有旅游的游记都位于 www.mafengwo.cn/mdd 下,按照城市进行了分类,每个城市的游记位于城市的首页。

城市的首页:/travel-scenic-spot/mafengwo/10774.html (路径一)

游记的分页格式:/yj/10774/1-0-01.html (路径二)

游记的页面:/i/3523364.html (leaf)

1
2
3
4
5
# 12141 城市代码
/yj/12141/1-0-[page].html

# 游记页面
/i/\d{7}.html

XPath

20:45

内容抽取的方式:XPath 和正则表达式。

安装

1
pip install lxml==3.6.4

基本语法

表达式 描述
nodename 选取此节点的所有子节点,tag 或 * 选择任意的 tag
/ 从根节点选取,选择直接子节点,不包含更小的后代(例如孙、从孙)
// 从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置,包含所有后代
. 选取当前节点
.. 选取当前节点的父节点
@ 选取属性

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from lxml import etree


s = '<a class="hask" href="http://www.taobao.com">Yes</a>'

tr = etree.HTML(s)

tr.xpath('//a')

tr.xpath('//a')[0]

tr.xpath('//a')[0].attrib

tr.xpath('//*[@class="hask"]')[0].attrib

Console

1
document.getElemeentsByClassNName('hasxjicon')

@ 属性

30:14

在 DOM 树中,以路径的方式查询节点,通过 @ 符号来选取属性。

1
<a rel="nofollow" class="external text" href="http://googlee.ac">google<wbr>.ac</a>
  • rel class href 都是属性,可以通过 "//*[@class='external text']"来选取对应元素。
  • = 符号要求属性完全匹配,可以用 contains 方法来部分匹配,例如"//*[contains(@class, 'external')]"可以匹配,而"//*[@class='external']"不能匹配。

运算符

36:07

and 和 or 运算符:

选择 p 或者 span 或者 h1 标签的元素

1
soup = tree.xpaht('//td[@class="editor bbsDetailContainer"]//*[self::p or self::span or self::h1]')

选择 class 为 editor 或者 tag 的元素

1
soup = tree.xpaht('//td[@class="editor" or @class="tag"]')

正则表达式

41:47

正则表达式是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个”规则字符串“,这个”规则字符串“用来表达对字符串的一种过滤逻辑。

在爬虫的解析中,经常会将正则表达式与 DOM 选择器结合使用。正则表达式适用于字符串特征比较明显的情况,但是同样的正则表达式可能在 HTML 源码里多次出现;而 DOM 选择器可以通过 class 及 id 来精确找到 DOM 块,从而缩小查找的范围。

NLP 自然语言处理

常用规则

43:58

符号 含义
\ 转义符,例如 \?
^ 字符串起始
$ 字符串结束
* 匹配前面子表达式0次或多次
+ 匹配前面子表达式1次或多次
? 匹配前面子表达式0次或1次
{n,m} 匹配至少 n 次,最多 m 次
. 匹配除 \n 之外的单个字符
(pattern) 匹配并获取这个匹配,例如匹配 ab(cd)e 正则表达式只返回 cd (匹配内容,并返回匹配内容。)
[xyz] 字符集合,匹配任意集合里的字符
[^xyz] 排除集合里的字符,不匹配集合里的字符
\d 匹配一个数字,等价 [0-9]

爬虫常用的正则规则

47:07

  • 获取标签下的文本: '<th[^>]*>(.*?)</th>'
  • 查找特定类别的链接,例如 /wiki/ 不包含 Category 目录:<a href="wiki/(?!Category:)[^/>]*>(.*?)<' (不包含 ?!Category:
  • 查找商品外链,例如 jd 的商品外链为7位数字的 a 标签节点:'/\d{7}.html'
  • 查找淘宝的商品信息,’ 或者 “ 开始及结尾:'href=[\"\']{1}(//detail.taobao.com/item.html[^>\"\'\s]+?)"'

贪婪模式及非贪婪模式

50:31

? 该字符紧跟在任何一个其他限制符 (*,+,?,{n},{n,},{n,m}) 后面时,匹配模式是非贪婪的。非贪婪模式尽可能少的匹配所搜索的字符串,而默认的贪婪模式则尽可能多的匹配所搜索的字符串。

示例

1
s = """<a href='https://www.google.com/search?q=regular+expression&oq=regular+expression&aqs=chrome..69i57j0l5.7173j0j7&sourceid=chrome&ie=UTF-8'>Google Test 1</a><a href='https://www.google.com/search?q=regular+expression&oq=regular+expression&aqs=chrome..69i57j0l5.7173j0j7&sourceid=chrome&ie=UTF-8'>Google Test 2</a>"""

对于上面的字符串,贪婪模式将匹配整个字符串。

1
re.findall('https://www.google.com/search\?q=.*UTF-8', s)

对于上面的字符串,而非贪婪模式才是我们想要的额,只返回一个链接

1
re.findall('https://www.google.com/search\?q=.*?UTF-8', s)

动态网页

动态页面的使用场景

52:19

  • 单页模式

    单页模式指的是不需要外部跳转的网页,例如个人设置中心经常就是单页

  • 页面交互多的场景 【去哪儿网】机票查询,特价机票,打折飞机票-去哪儿网Qunar.com

    一部分网页上,有很多的用户交互接口,例如去哪儿的机票选择页面,用户可以反复修改查询的参数

  • 内容及模块丰富的网页

    有些网页内容很丰富,一次加载完对服务器压力很大,而且这种方式延时也会很差;用户往往也不会查看所有内容

动态页面带来的挑战

56:41

对于爬虫:

  • 简单下载 HTML 已经不行了,必须得有一个 Web 容器来运行 HTML 的脚本
  • 增加了爬取的时间
  • 增加了计算机的 CPU、内存的资源消耗
  • 增加了爬取的不确定性

对于网站:

  • 为了配合搜索引擎的爬取,与搜索相关的信息会采用静态方式
  • 与搜索无关的信息,例如商品的价格、评论,仍然会使用动态加载

抓取动态页面-分析

1:01:39

打开目标页面后,直接右键点击,只保存 HTML

【三星Galaxy S8】【限量礼盒版】三星 Galaxy S8(SM-G9500)4GB+64GB 谜夜黑 移动联通电信4G手机 双卡双待【行情 报价 价格 评测】-京东

分析动态网页-对比

1:04:52

  • 使用 BeyondCompare 或者 SVN 等工具,来对比网页,大致找出动态加载的部分

  • 针对要提取的部分,分别查看 html only 与 full webpage,找出动态数据的部分

  • 记录下它们的 class 或 id,试着用代码来提取,如果不能提取,说明是动态的:

    1
    2
    3
    4
    5
    6
    7
    8
    from lxml import etree

    f = open('./s7-full.htm')
    c = f.read().decode('gbk')
    f.close()

    e = etree.HTML(c)
    print e.xpath(u'//span[@class="price J-p-10524731933"]')

问题解答

1:08:55 休息5分钟

  • bloomfilter 数据持久化,直接写到文件就行。

  • github 地址:junglelord/spider-course-2

    第一次课是按照课程来组织代码的,第二次课就按照项目名来组织文件名。

  • headless chrome,phantomjs 已经不维护了。

  • 文本相似度

    词向量 wordback 文本分析处理,需要机器学习、神经网络方面的知识要求比较高。

Python Web 引擎

1:18:57

  • PyQt PySide:基于 QT 的 Python Web 引擎,需要图形界面的支持,需要安装大量的依赖。安装和配置复杂,尤其是安装图形系统,对于服务器来说代价很大
  • Selenium:一个自动化的 Web 测试工具,可以支持包括 Firefox、Chrome、PhantomJS、IE 等多种浏览器的连接与测试
  • PhantomJS:一个基于 Webkit 的 Headless 的 Web 引擎,支持 JavaScript。相比 PyQt 等方案,PhantomJS 可以部署在没有 UI 的服务器上。

PhantomJS + Selenium

安装 Selenium

1
pip install selenium

安装 PhantomJS

1
brew cask install phantomjs

使用 PhantomJS 来加载动态页面

1:25:02

1
2
3
4
5
6
7
8
9
10
11
12
from selenium import webdriver


driver = webdriver.PhantomJS(service_args=['--ignore-ssl-errors=true'])

# 设置浏览器窗口大小
driver.set_window_size(1280,2400)

driver.get(cur_url)

# 查看页面 html 源代码
content = driver.page_source

SocketIO 跟 云端保持长连接。

set_window_size

1:32:48

对于动态网页,有可能存在大量数据是根据视图来动态加载的,PhantomJS 允许客户端设置用来模拟渲染页面的窗口的尺寸,这个尺寸如果设置比较小,我们就不得不用 JavaScript 的 scroll 命令来模拟页面往下滑动的效果以显示更多内容,所以我们可以设置一个相对大的窗口高度来渲染。

1
driver.set_window_size(1280,2400)

Built-in DOM selector

1:34:37

Selenium 实现了一系列的类似与 XPath 选择器的方法,使得我们可以直接调用 driver.find_element()来进行元素的选择,但是这些都是基于 Python 的实现,执行效率非常低,大约是基于 C 的正则表达式或 lxml 的10倍的时间,因此不建议使用 built-in 的选择器,而是采用 lxml 或者 redriver.page_sourcehtml 文本)进行操作。

1
2
3
4
find_element(self, by='id', value=None)
find_element_by_class_name(self, name)
find_element_by_id(self, id_)
find_element_by_css_selector(self, css_selector )

Useful Methods & Properties

1:37:40

Selenium 通过浏览器的驱动,支持大量的 HTML 及 JavaScript 的操作,常用的包括:

  • page_source:获取当前的 HTML 文本
  • title:HTML 的 title
  • current_url:当前网页的 URL
  • get_cookie() & get_cookies():获取当前的 cookie
  • delete_cookie() & delete_all_cookies():删除所有的 cookie
  • add_cookie():添加一段 cookie
  • set_page_load_timeout():设置网页超时时间
  • execute_script():同步执行一段 JavaScript 命令
  • execute_async_script():异步执行 JavaScript 命令

HTML title 和 文章 title 不一样。

Close and Clear

1:40:41

Selenium 通过内嵌的浏览器 driver 与浏览器进行通信,因此在退出的时候必须调用 driver.close()driver.quit()来退出 PhantomJS。否则 PhantomJS 会一直运行在后台并占用系统资源。

查看进程

1
ps aux|grep phantomjs

关闭进程

1
2
3
4
# 官方建议
driver.service.process.send_signal(signal.SIGTERM)

driver.close()
1
pgrep phantomjs | xagrs kill

提取动态数据

1:49:17

  1. 加载的过程中,根据网络环境的好坏,会存在一些延时,因此要多次尝试提取。
  2. 动态页面的元素,同一个元素可能命名方式有很多种,要多种情况都进行尝试。如果一个元素不能找到而且 Selenium 并没有报网络错误,可能元素有新的命名了,我们需要将找不到的页面及元素信息记录在日志里,使得后续可以分析,找出新的定义的方法并对这一类页面重新提取信息。

提取动态数据代码

1:50:57

1
2
3
4
5
6
7
8
9
while number_tried < constans['MAX_PAGE_TRIED']:
try:
price_element = driver.find_element_by_class_name('' % (item_id))
break
except selenium.common.exceptions.NoSuchElementException, msgs:
number_tried += 1
print msgs[1]
except Exception, msgs:
print msgs[1]

href 的类型 \ and \\

1:51:08

网页内,href 后面的链接可能有这样三种:

  • href=”http://career.taobao.com"

    完整 URL,直接跳转

  • href=”\\detail.taobao.com\iuslkjsd” (淘宝)

    协议相关的绝对路径,如果现在是 https://xxx 则需要在 \\detail.taobao.com\iuslkjsd前面加上 https:,协议也可以是 file:或者 ftp:,这种比较灵活。

  • href=”\i\8277375.html” (京东)

    网站的相对路径,需要在前面指明当前 url 的协议及 domain

PhantomJS 配置

1:53:15

  • –ignore-ssl-errors=[true|false] 一些证书没有获得 CA 授权,浏览器会报错,使用这个命令后,能自动忽略此类错误。
  • –load-images=[true|false] 加载图片(不加载)
  • –disk-cache=[true|false] 硬盘缓存 (开启硬盘缓存)
  • –cookie-file=/path/to/cookies.txt cookie文件存放位置
  • —debug=[true|false] debug模式
  • –config=/path/to/config.json 指定配置文件

证书授信 CA SSL HTTP

1:54:27

SSL 非对称的加密方式

  • private key 服务器使用私钥解密信息。
  • public key 公钥公布。

PhantomJS config

2:01:37

–config=/path/to/config.json

1
2
3
4
{
"ignoreSslErrors": true,
"maxDiskCacheSize": 1000
}

总结

2:03:56

答疑

2:05:27

  • js 滚动:window.scrollTo(0, 4000)

    1
    document.body.scrollHeight
  • 滑动验证码 破解方案

03 微博的抓取

讲课开始时间:2017-06-15 19:52

09:22 开始讲课

分布式爬虫

大纲

  • 使用 Selenium + PhantomjS 来抓取
  • 微博接口分析
  • 直接调用微博 API 来抓取
  • 表单及登录

10:48 流程分析

登录

13:03

最重要的是 设置 User-Agent,否则无法跳转链接

1
2
3
4
5
6
7
8
9
10
11
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities


user_agent = (
)

dcap = dict(DesiredCapabilities.PHANTOMJS)

dcap["phantomjs.page.settings.userAgent"] = user_agent

driver = webdriver.PhantomJS(desired_capabilities=dcap)

输入用户名与密码

15:49

为了与微博内容交互,需要使用 JavaScript

1
2
3
4
5
6
7
<input id="loginname" class="W_input " maxlength="128" autocomplete="off" action-data="text=邮箱/会员帐号/手机号" action-type="text_copy" name="username" node-type="username" tabindex="1" type="text">	

<input class="W_input " maxlength="24" autocomplete="off" value="" action-type="text_copy" name="password" node-type="password" tabindex="2" type="password">

<input class="W_input " maxlength="6" autocomplete="off" value="验证码" action-data="text=请输入验证码" action-type="text_copy" name="verifycode" node-type="verifycode" tabindex="3" type="text">

<div class="info_list login_btn"><a href="javascript:void(0)" class="W_btn_a btn_32px" action-type="btn_submit" node-type="submitBtn" suda-data="key=tblog_weibologin3&amp;value=click_sign" tabindex="6"><span node-type="submitStates">登录</span></a></div>

相关的 JavaScript 代码

1
2
3
4
5
6
7
8
9
// 输入用户名
document.getElementById('loginname').value='13715221811'

// 输入密码
document.getElementsByName("password")[0].value="wowooewweojfwekjnf"
document.getElementsByClassName("W_input ")[2].value="12345623432"

// 点击登录按钮
document.getElementsByClassName("W_btn_a btn_32px")[0].click()

通过 Selenium 提供的 send_keys 来传递 value

1
2
driver.find_element_by_id('loginname').send_keys(username)
driver.find_element_by_name('password').send_keys(password)

分析微博网站结构

29:22

  1. 找出感兴趣的微博号,翻页抓取全部。(起始点)(关注,粉丝,微博)
  2. 随机一个页面,遍历所有页面。(关注了谁,谁关注了他)

关注、分析

40:28

关注列表、粉丝列表,作为漫游 Weibo 的外链。

  • 要选择什么数据做指标,高质量的关注列表

  • 翻页对数据库有很大的负担,有深翻页的行为的一般都是机器人

获取微博外链

46:17

1
driver.find_element_by_xpath('//a[@class="t_link S_txt1"]')

0是关注,1是粉丝,2是微博,我们只需要0,关注的微博一般是有质量的额,而粉丝的数量太多,并且有太多僵尸粉。

打开关注列表页:

1
driver.find_element_by_xpath('//a[@class="t_link S_txt1"]').get_attribute("href")

获取所有关注的微博号的地址:

1
driver.find_element_by_xpath(‘//*[contains(@class, "follow_item")]//a[@class="S_txt1"]')

获取微博用户信息

48:13

  • 提取用户的基本信息

    • 链接:用正则表达式把用户的链接参数都去掉/u/1634431184?refer_flag=1005050006,只保留/u/1634431184`
    • 微博昵称及头像
    • 关注、粉丝及微博数量
  • 过滤质量差的用户,对于微博数量少于阀值,或者关注数超过粉丝数 N 倍以上的,判定为僵尸粉或广告微博,直接跳过(对于爬取的效率有很大影响)

    • 僵尸粉:微博数量极少
    • 纯广告、营销微博:关注数远远超过粉丝数量
  • 提取下一页,可以继续查找更多的 user

判断关注数和粉丝数是否作为我们的种子用户。

判断微博数小于某个数量,大于某个数量的。不要去爬取。(效率)

一般我们聚焦在某个行业的内容。

微博信息抽取 - 微博正文抽取

1:01:29 第二次讲到 1:16:01

微博名:driver.find_element_by_tag_name('h1')

所有的 Feed:driver.find_elements_by_class_name("WB_detail")

1
2
3
4
5
6
7
feed = {}
feed['time'] = element.find_element_by_xpath('.//div[@class="WB_from S_txt2"]').text
feed['content'] = element.find_elelment_by_class_name('WB_text').text

feed['image_names'] = []
for image in element.find_elements_by_xpath('.//li[contains(@class,"WB_pic")]/img'):
feed['image_names'].append(re.findall('/([^/]+)$', image.get_attribute('src')))

微博的图片,只需要保存图片名

http://wx2.sinaimg.cn/thumb150/4b7a8989ly1fcws2sryvrj22p81sub2a.jpg

http://存储域名/分辨率/文件名

个人信息,在数据库中不会存完整的链接,只会保存文件名,然后请求的时候在做拼接。

图片上传,服务器一般会存三个格式,一个缩略图,一个中等大小的图片,一个原图。

1
2
3
4
5
6
7
8
9
10
11
12
def extract_user

def go_next_page(cur_driver):
try:
next_page = cur_driver.find_element_by_xpath('//a[contains(@class, "page next")]').get_attribute('href')
print 'next page is ' + next_page
cur_driver.get(next_page)
time.sleep(3)
return True
except Exception:
print 'next page is not found'
return False

爬虫1:爬取所有 user_id

爬虫2:爬取正文(1.翻页,2.提取数据,3.下一页)

微博图片信息

1:13:05

1
re.findall('/([^/]+)$', image.get_attribute('src'))

服务器上图片存储规格,三种:缩略图、中图、大图(原图)

滚动频率与翻页

每次滚动后,检查是否已经出现了

  • 微博的下一页的 class: page next S_txt1 S_line1

    1
    driver.find_element_by_xpaht('//a[@class="page next S_txt1 S_line1]').click()
  • 翻页命令

    1
    driver.execute_script('window.scrollTo(0, document.body.scrollHeight)')
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
def extract_feed(feeds):
for i in range(0,20):
# 滚动到页面底部,使得当前页面所有的微博都被显示
scroll_to_bottom()

# 遍历这个数组,提取所有微博的内容
for element in feeds_crawler.find_elements_by_class_name('WB_detail'):
tried = 0
while tried < 3:
try:
feed = {}
feed['time'] = element.find_element_by_xpath('.//div[@class="WB_from S_txt2]').text
feed['content'] = element.find_element_by_class_name('WB_text').text
feed['image_names'] = []
for image in element.find_elements_by_xpath('.//li[contains(@class,"WB_pic")]/img'):
feed['image_names'].append(re.findall('/([^/]+)$', image.get_attribute('src')))
feeds.append(feed)
# print '-----------------'
print -*20
print feed['time']
print feed['content']
break
except Exception:
tried += 1
time.sleep(1)
if go_next_page(feeds_crawler) is False:
return feeds


def scroll_to_bottom():
# 最多尝试20次滚屏
print ' scroll down'
for i in range(0, 50):
# print 'scrolling for the %d time' % (i)
feeds_crawler.execute_script('window.scrollTo(0, document.body.scrollHeight)')
html = feeds_cralwer.page_source
tr = etree.HTML(html)
next_page_url = tr.xpath('//a[contains(@class, "page next")]')
if len(next_page_url) > 0:
return next_page_url[0].get('href')
if len(re.findall('点击重新载入', html)) > 0:
print 'scrolling failed, reload it'
feeds_crawler.find_element_by_link_text('点击重新载入').click()
time.sleep(1)

1:22:39

每次滚动后,检查是否已经出现了”下一页“的按钮,如果是则停止翻页,否则检查是否出现了”网络超时“的链接,是的话,点击这个链接来重新加载。

  1. 滚屏
  2. 提取
  3. 翻页

滚屏

1:23:45

微博抓取框架 - 重点

1:24:25

流程图

Web 1: Crawler

  • Dequeue Url:从数据库拿数据

Web 2: User Info

  • Enqueue Url:数据放到数据库

1:27:31

爬虫操作 chrome 浏览器访问 weibo。

robot->chrome->weibo

效率比较低

1:30:03 休息一下

1:38:32 JavaScript 问题 <a href="javascript:">空 js 代码可能会触发事件然后处理。

微博接口分析

1:44:26

微博域名 http://m.weibo.cn

这是微博的首页网页版,能看到结构非常简单,我们能直接拿到 Feed 流,我们尝试从移动端来分析微博的数据 API 接口

GET

protocol + domain + path + parameters

http:// + m.weibo.cn + /u/14968146565/

个人首页

1:56:33

参数很多,有些参数不是必要的,可以省略。

https://m.weibo.cn/api/container/getIndex?type=uid&value=1627825392&containerid=1076031627825392&since_id=4334132933228564

https://m.weibo.cn/api/container/getIndex?type=uid&value=1627825392&containerid=1076031627825392&since_id=4333519469486774

个人 feed 流

2:01:06

ajax 请求:https://m.weibo.cn/api/container/getIndex?type=uid&value=1627825392&containerid=1076031627825392

  • type:通过 uid 方式查询
  • value: user id
  • containerid:107603 + user id
  • 翻页:since_id

点击打开个人关注页

2:07:05

有三类关注:

个人主页 https://m.weibo.cn/profile/1627825392

爬取流程

2:09:24

headers 通过 postman interceptor 提取。

1
def crawl_users

2:17:23 数据库

Mongo No_SQL Document 文档型数据库(JSON 数据)

MySQL SQL 关系型数据库

2:18:35 MongoDB 操作

1
2
3
4
5
6
7
8
9
10
11
# 查看数据库
show dbs

# 使用 spider 数据库
use spider

# 查看表
show tables

# 查看数据表 weibo 的数据,只看1行
db.weibo.find().limit(1)

JSON Editor Online - view, edit and format JSON online

总结

2:22:14

  • 两种方式去分析微博的数据抓取策略
  • 2个爬虫去抓,一个抓用户的信息,一个抓微博内容。

答疑

2:25:23

  • 106703 这个数是通过观察 url 的规则找到的。
  • 反爬虫,后边会讲。
  • 表单模拟登录下一节课讲。
  • 微博搜索接口 http://s.weibo.cn

zookeeper + kafka + rabbitMQ,课程有可能会讲。

04 多线程与多进程的爬虫

讲课开始时间:2017-06-20 20:03

00:41 开始讲课

大纲

00:50

  • 表单及登录
  • 多线程及语言分析对比
  • 多进程爬虫

表单及登录

HTML 提交数据

01:55

21:21

  • form 表单

    1
    2
    3
    <form>
    <input type="text" name="username">
    </form>

    HTML 的标签,由浏览器实现 POST 方法

  • ajax 请求

    1
    2
    $.(ajax){
    }

    基于 ajax 技术,异步发送 http 请求并获得返回数据,然后利用 JavaScript 对网页进行处理。

10:28

client:cookie

server:session

服务器响应后,发给浏览器 Set-Cookie。

后台处理流程

15:55

一个请求,请求前(before dispatch)、请求后(after dispatch)。响应

  • 请求
  • 处理
  • 响应

cookie 是会过期的,服务器可以设置有效期。

HTML 表单

27:35

表单使用<form></form> tag 包围

里面包含了表单的元素:

<select> <textarea> <input>

` type 包含了多种输入类型,例如:

  • <input type="text" name="name"> 文本输入框
  • <input type="password" name="password"> 密码输入框
  • <input type="submit" value="Sublimt"> 提交按钮

表单类型: form-data

29:44

Content-Type: multipart/form-data;,登录是不会使用这种方式的。

表单类型:x-www-form-urleencoded

32:02

51:20

Content-Type: applicationn/x-www-form-urlencoded

会将表单内的数据转换为键值对。

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
import cookielib
import urllib2
import urllib


url = ""

headers = {
'host': '',
"user-agent": '',
"content-type": "application/x-www-form-urlencoded"
}

if __name__ == "__main__":
data = {'name': 'caca', "password": "c"}
payload = urllib.urlencode(data)

cj = cookielib.CookieJar()
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))
request = urllib2.Request(url, payload, headers=headers)
response = opener.open(request)
print response.info()
print response.read()
for cookie in cj:
print cookie.name, cookie.value, cookie.domain

ajax 登录

35:26

一般以 json 的方式提交数据

  • 淘宝网类似

登录

38:44

  1. 根据 chrome inspector 里检查到的参数,来设置登录方式
    • 最常用的是 x-www-form-urlencoded 和 json 方式,两种方式只是 body 编码不同
    • 如果是 form-data,按照 form-data 的方式组合 body
    • body 部分经常需要安装特定的方式编码,比如 base64,或者 md5
    • 对于非常复杂的登录协议,利用动态网页方式登录。(利用 Selenium 登录,把 cookie 拿出来,给后面的爬虫使用)
  2. request = urllib2.Request(url, data, headers=loginheaders)
    • url 是访问的地址
    • data 是 body 部分
    • headers=loginheaders 是 HTTP 请求的 HEADER 数据

表单登录 Python 代码

42:40

1
2
3
4
5
6
7
8
9
10
11
import urllib2
import urllib

url= ""
headers = {
"content-type": "application/x-www-form-urlencoded"
}
data = {'name': 'caca', "password": "c"}
payload = urllib.urlencode(data)
request = urllib2.Request(url, payload, headers=headers)
response = urllib2.urlopen(request)

意外??

43:11

当遇到网页登录后,返回302跳转的情况下,urllib2 的 Response 会丢失 Set-Cookie 的信息,导致登录不成功。

通常的情况下,我们需要一个通用的能处理 Cookie 的工具

  • 自动处理 Set-Cookie 请求
  • 自动处理管理过期 Cookie
  • 自动在对应的域下发送特殊 Cookie

urllib2 插件

44:48

urllib 实现核心功能,附加功能会提供一个插件(Hook)

53:44

登录的核心是为了获得 Cookie。

使用 urllib2 的插件功能

54:16

urllib2 可以绑定一系列的处理对象 handler。

  • HTTPRedictHandler 用来处理跳转的情况。
  • ProxyHandler 用于处理代理。

CookieJar

55:54

1
2
3
4
5
6
7
8
cj = cookielib.CookieJar()
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))
request = urllib2.Request(url, payload, headers=headers)
response = opener.open(request)
print response.info()
print response.read()
for cookie in cj:
print cookie.name, cookie.value, cookie.domain

多线程爬虫

1:00:10

多线程,单线程都是运行在同一内存空间。

内存空间是共享的,共享的资源要有保护。

多线程的复杂性

1:02:01

  • 资源、数据的安全性:锁保护
  • 原子性:数据操作是天然互斥的
  • 同步等待:wait() notify() notifyAll()
  • 死锁:多个线程对资源互锁,造成死锁
  • 容灾:任何线程出现错误,整个进程都会停止

画个图

1:04:02

线程:一个线程取流媒体数据,一个线程播放视频到屏幕。

多线程的优势

1:05:20

  • 内存空间共享,信息数据交换效率高
  • 提高 CPU 的使用效率
  • 开发便捷
  • 轻,创建、销毁的开销小

Python 线程

1:07:08

  • 支持多线程(JavaScript PHP 不支持多线程)
  • Python 线程直接映射到 native 线程(Java 1.4 的 Java 线程是 JVM 实现的,共同运行在一个 native thread)
  • GIL(Global Interpretor Lock):对于多核的利用能力有限。

爬虫的瓶颈是 I/O,I/O Bound

实现一个多线程爬虫

1:15:28

  1. 创建一个线程池 threads = []
  2. 确认 url 队列线程安全 Queue Deque
  3. 从队列取出 url,分配一个线程开始爬取 pop()/get() threading.Thread
  4. 如果线程池满了,循环等待,直到有线程结束 t.is_alive()
  5. 从线程池移除已经完成下载的线程 threads.remove(t)
  6. 如果当前级别的 url 已经遍历完成,t.join()函数等待所有现场结束,然后开始下一级别的爬取。

多线程爬虫评价

1:18:41

优势:

  • 有效利用 CPU 时间
  • 极大减小下载出错、阻塞对抓取速度的影响,整体上提高下载的速度
  • 对于没有反爬虫限制的网站,下载速度可以多倍增加

局限性:

  • 对于有反爬的网站,速度提升有限
  • 提高了复杂度,对编码要求更高
  • 线程越多,每个线程获得的时间就越少,同时线程切换更频繁也带来额外开销
  • 线程之间资源竞争更激烈

使用 threading,不要使用 thread。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
for t in threads:
if not t.is_alive():
threads.remove(t)
if len(threads) >= max_threads:
time.sleep(CRAWL_DELAY)
continue
try:
t = threading.Thread(target=get_page_content, name=None, args=(url,))
threads.append(t)
t.setDaemon(True)
t.start()
time.sleep(CRAWL_DELAY)
break
except Exception:
print "Error: unable to start thread"

多进程爬虫

1:25:17

线程与进程

进程开销比较大,数据是独立的,进程之间切换比线程切换慢。

进程之间数据共享,

多进程爬虫评估

1:29:50

目的:

  • 控制线程数量
  • 对线程进行隔离,减少资源竞争
  • 某些环境下,在单机上利用多个 IP 来伪装

局限性:

  • 不能突破网络瓶颈
  • 单机单 IP 的情况下,变得没有意义
  • 数据交换的代价更大

进程间通信

1:30:00

  • 管道(PIPE)
  • 信号(Signal):复杂
  • 消息队列:Posix 及 system V
  • 共享内存:速度最快,需要结合信号量达到进程间同步及互斥
  • 信号量:用于数据同步
  • Socket:可以标准化,可以用于多机

Android 进程间通信 Binder

1:30:47

Android 进程间通信 AIDL

1:32:01

创建多进程爬虫

1:36:07

Solution A - C/S 模式

  1. 一个服务进程,入队及出队 URL,入队需检查是否已经下载
  2. 监控目前的爬取状态、进度
  3. 多个爬取进程,从服务进程获取 URL,并将新的 URL 返回给服务进程
  4. 使用 Socket 来做 IPC

注册与发现,新客户端被创建,服务端可以发现。远程管理、队列管理、错误维护、

Solution B - 数据库模式

  1. 使用数据库来读取爬取列表
  2. 多个爬取进程,URL 的获取与增加都通过数据库此操作

C/S v.s. 数据库

1:38:44

CS 优势:

  • 运行速度快,添加、修改、查询都是内存的 BIT 位操作
  • 扩展方便,例如动态 URL 队列重排

数据库:

  • 开发便捷,数据库天生具备读写保护及支持 IPC
  • 只需要写一个爬虫程序

堆内的存储(CPU 内,代码空间内)、堆外的存储,外存,网络存储。访问速度呈指数级别的下降。

创建 MySQL 数据库表

1:42:03

Key Type Description
index int(11) PRIMARY KEY AUTOINCREMENT
url varchar(512) UNIQUE
status varchar(11) download status: new,downloading,done
md5 varchar(16) UNIQUE md5 value of url
depth int(11) page depth
queue_time timestamp CURRENT_TIMESTAMP time url enqueue
done_time timestamp time page downloaded

数据库创建

1:42:33

1
2
3
4
5
6
7
8
9
10
11
CREATE TABLE urls (
index int(11) NOT NULL AUTOINCREMENT,
url varchar(512) NOT NULL,
status varchar(11) NOT NULL DEFAULT new,
md5 varchar(16) NOT NULL,
depth int(11)NOT NULL,
queue_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
done_time timestamp NOT NULL DEFAULT 0 ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY index,
UNIQUE KEY md5
)ENGINE=InnoDB

ON UPDATE CURRENT_TIMESTAMP 不是所有版本的 MySQL 数据库都支持。老版本不支持。

Python MySQL Connector

1:43:40

  • 使用 MySQLConnectionPool 来管理多线程下的 MySQL 数据库链接

    1
    2
    mysql.connector.poolinng.MySQLConnectionPool
    self.cnxpool.get_connection()
  • __init__ 类实例的时候自动检查和创建数据库及表

  • Cursor 类:cursor = con.cursor(dictionary=True)

  • SELECT ... FOR UPDATE 加读锁,避免多个进程取出同一个 url

  • cursor.commit()支持事务,默认关闭了 autocommit 因此需要提交

ConnectionPool 去链接数据库。数据库优化问题

MySQL :: MySQL Connector/Python Developer Guide

1:48:49 课程结束

1:48:51 下节课预热

  • CS 模式爬虫
  • Zookeeper 分布式系统管理发现。

总结

1:49:47

解答

1:51:56

  • Nginx
  • HBase
  • GIL 锁
  • MySQL 访问的库 1:56:00 mysqlmanager.py

Python Mysql Connector

1
pip install pymysql

Python Mysql Connector
py2.7

1
pip install mysqlclient

05 微博数据的存储:分布式数据库及应用

讲课开始时间:2017-06-22 19:53

开始讲课 08:05

主要讲爬虫用的数据库存储方式。

大纲

  • 一个简单的分布式爬虫
  • 分布式存储
  • 分布式数据库及缓存
  • 完整的分布式爬虫

分布式爬虫系统

08:36

降低 I/O ,防火墙限制流量就被反爬虫了。

爬虫的两个最典型应用方向,搜索引擎,NPL(自然语言处理)

分布式爬虫的作用

16:59

  • 解决目标地址对 IP 访问频率的限制
  • 利用更高的带宽,提高下载速度 (解决 I/O bond)
  • 大规模系统的分布式存储和备份 (数据安全问题)
  • 数据的扩展能力

将多进程爬虫部署到多台主机上

19:48

  • 将数据库地址配置到统一的服务器上

  • 数据库设置仅允许特定 IP 来源的访问请求

    1
    GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY 'password' WITH GRANT OPTION; FLUSH PRIVILEGES;

    vim .cnf

    1
    # bind-address = 127.0.0.1
  • 设置防火墙,允许端口远程连接

    1
    iptables -A INPUT -i eht0 -p tcp -m tcp --dport 3306 -j ACCEPT

Mastere Slave 方式的爬虫

DB 方式的爬虫

分布式存储

31:32

使用 HDFS 存储抓回来的网页源码

MySQL:可以存任务调度

爬虫原始数据存储特点

32:47

  • 文件小,大量 KB 级别的文件
  • 文件数量大
  • 增量方式一次性写入,极少需要修改
  • 顺序读写
  • 并发的文件读写
  • 可扩展

Google FS

36:30

HDFS 是 Google FS 的一个开源实现。

大量小文件组成一个数据块然后写入到一个磁盘上。

随机读取数据很差

HDFS 架构

  • 数据节点(Data Node)(存数据的系统)
  • 名字节点(Name Node)

Python hdfs module

42:42

Python hdfs module

1
pip install hdfs

HBASE

44:13

  • On top of HDFS
  • Column-oriented database
  • Can store huge size raw data
  • KEY-VALUE

HBASE 存储 网页文件

AWS - S3

阿里云 - OSS

列数据库

行数据库-MySQl

HBASE 和 HDFS 比较

50:13

HDFS 分布式存储系统,HBASE 数据库。

HBASE 样子

52:03

  • tables
  • column families
  • column:Key-Value 键值对

不是讲 HADOOP,而是讲爬虫是怎么用 HBASE 的。

HBASE 存储架构

55:00

HRegion->MemStore(Column Falimilies)

我们怎么去怎么去定义 Column Falimilies(CF)?

key,CF 网页源码(数据量大),CF 网页地理信息(数据量小)

一个 store 只放一个 CF,

HBASE 框架图

59:39

结合代码去学 HBASE 怎么使用

1:00:18

hbasemgr.py

一般使用 batchdata 方式,网络连接传输是有代价的。

一次写入一批数据。

HBASE 自己搭建。

存储方案

1:05:58

MySQL/MongoDB:存任务 + 结构提取后的数据(信息)

HBASE:存网页源待码

Redis:做告诉缓存

1:07:35 休息下,答疑

安装 HBASE Python 库 HappyBase — HappyBase 1.1.0 documentation

1
pip install happybase

Hadoop 教程:Hadoop Tutorial

  • MySQL 的 QPS,建立连接到连接被释放掉。

  • Redis 要做持久化

MongoDB

1:16:09

关系型数据库和 MongoDB 的数据类型对比

RDBMS MongoDB
Database Database
Table Collection
Tuple/Row Doucment
column Field
Table Join Embedded Documents(就在文档内)
Primary Key Primary Key (Default key _id provided by mongodb itself)

非关系型数据库的优势

1:21:12

  • Schema less:没有规则,列数不一定
  • Ease of scale-out:很容易扩展

MySQL 典型的 B+ 树结构。分表分库。

1:27:45 簇分片 Sharels(读写分离 模式)(replica 模式)

MongoDB 安装

1:29:16

1
2
3
4
5
6
7
show dbs

use 数据库名

show tables

db.数据库名.find().limit(1)

pymongo

1:30:50

1
pip install pymongo

Python 操作 Mongo

1
db.collection.findOneAndUpdate(filter, update, options)

MongoDB 存储 任务队列,

  • 事务操作,把 n 个操作构成一个原子操作。(收入、库存、购票信息一起操作完成,要么一个失败,所有的都失败。)
  • 锁的概念,读锁和写锁。阻塞。
  • MySQL InnoDB 支持行级锁,MyISAM 只能做表级锁。

使用 Mongo 存储微博的数据。

数据库类型

1:38:10

Type Databases
RDBMS Oracle,MySQL
Key-Value BerkeleyDB
In Memory Key-Value MemoryCached,Redis
Document MongoDB
Column HBase
Graphic Neo4j, Titan(支持分片)

1:40:55 一种存任务队列,相比于 MySQL 没有上限限制。

Redis

1:42:13

  • 基于 KEY VALUE 模式的内存数据库
  • 支持负载的对象模型(MemoryCached 仅支持少量类型)
  • 支持 Replication,实现集群(MemoryCached 不支持分布式部署)
  • 所有操作都是原子性(MemoryCached 多数操作都不是原子的)
  • 可以序列化到磁盘(MemoryCached 不能序列化)

Redis 安装

1:47:07

1:48:20 Python Redis

1
pip install redis

操作

1
2
3
4
5
6
7
import redis

r = redis.StrictRedis(host='localhost', port=6379, db=0)

r.set('foo', 'bar')

r.get('foo')

MongoDB 的优化

1:48:55

  • url 做为 _id,默认会被创建索引,创建索引是需要额外的开销的
  • index 尽量简单, url 长了一些
  • dequeueUrl find_one() 并没有利用 index,会全库扫描,但是仍然会很快,因为扫描到第一个后就停止了,但是当下载完成的数量特别大的时候,扫描依然是很费时的,考虑一下能不能进一步优化
  • 插入的操作很频繁,每一个网页对应着几百次插入,到了 depth = 3 的时候,基数网页是百万级,插入检查将是亿级,考虑使用更高效的方式来检查。

Mongo with Redis ,Redis 做缓存级别的查询

1:51:56

CPU cache>Heap>Redis>Disk>LAN>WAN

代码

1:58:32

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
if self.db.mfw.count() is 0:
self.db.mfw.create_index('status')


deef enqueuUrl(self, url, status, depth):
if self.redis_client.get(url) is not None:
return
self.redis_client.set(url, True)
record = {
'url': url,
'status': status,
'queue_time': datetime.utcnow(),
'depth': depth
}
self.db.mfw.insert({
'_id': hashlib.md5(url).hexdigest()},
{'$set': record
})

redis 有 list 类型,list 如何实现的,一种是 array-list(随机查找很快)(增删很慢),一种是 linked-list(插入,删除,很快)。 qlist(链表 + 数组)

2:03:33 课程结束

答疑

2:04:01

  • redis 崩溃数据的安全,第一种方式,redis 定时增量持久化。第二种方式,通过日志系统。
  • 协程的概念老师也不太懂
  • scrapy 定时爬,apscheduler

06 多机并行的微博抓取:分布式系统设计

讲课开始时间:2017-06-27 19:54

开始讲课 08:03

大纲

08:13

  • 分布式系统概述
  • 主从服务设计

分布式系统

10:31

开发分布式系统流程

分布式系统的优势

15:34

分布式系统遇到的挑战

19:52

关于很多数据之间的迁移。

分布式爬虫系统

28:51

  • master 干什么事情
  • 制定协议 master 和 slave 通信(队列检查,通过 redis 检查队列,)

Master-Slave 结构

37:14

  • 有一个主机,对所有的服务器进行管理,绝大多数分布式系统,都是 Master-Slave的主从模式。
  • 当爬虫服务器多的时候,必须能通过一个中心节点对从节点进行管理
  • 能对整体的爬取进行控制
  • 爬虫之间信息共享的桥梁
  • 负载控制

Remote Procedure Calls

39:12

Protocol - Message Type

43:44

MSG_TYPE = ‘TYPE’

REGISTER = ‘REGISTER’

UNREGISTER = ‘UNREGISTER’

HEARTBEAT = ‘HEARTBEAT’

PAUSED = ‘PAUSED’

RESUMED = ‘RESUMED’

SHUTDOWN = ‘SHUTDOWN’

Protocol - Actions

48:29

ACTION_REQUIRED = ‘ACTION_REQUIRED’

PAUSE_REQUIRED = ‘PAUSE_REQUIRED’

RESUME_REQUIRED = ‘RESUME_REQUIRED’

SHUTDOWN_REQUIRED = ‘SHUTDOWN_REQUIRED’

Protocol - Key Definition

51:07

SERVER_STATUS = ‘SERVER_STATUS’

// SPIDER 的 ID

CLIENT_ID = ‘CLIENT_ID’

ERROR = ‘ERROR’

51:55 client_crawler.py

爬虫线程

58:51

主线程:检查状态,创建爬出

爬虫线程:爬取完成退出

心跳线程:维护状态

Socket 编程

1:03:53

TCP client

TCP Server

创建 socket client

1:06:25

1
s = socket.create_connection(socket.AF_INET, socket.SOCK_STREAM)

AF_INET:IPv4 协议

SOCK_STREAM:TCP (保证连接)还是 UDP (速度快)

创建 socket server

1:07:44

1
2
3
4
5
6
7
8
9
10
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

server_socket.bind(socket.gethostname(), 20010)

server_socket.listen(5)

while True:
(client_socket, address) = server_socket.accept()
server ct = client_thread(client_socket)
ct.run()

Ways to listen

1:10:16

  • 新建一个线程
  • 新建一个进程 (Apache)
  • 使用无阻塞 socket

Non-blocking mode listening

1:13:00

  • connection.setblocking(False)
  • asyncore

停止 ways to end communication

1:14:42

  • fixed length message

    1
    2
    3
    4
    5
    6
    7
    8
    data = []

    total_len = 0

    while total_len < MSG_LEN:
    char = conn.recv(1)
    data.append(char)
    total_len += 1
  • 加上结束符 delimited

  • indicates messagee length in beginning 设置字符长度

  • shutdown connection:server call close(), client recv() returns 0

socket_server.py

server socket, client socket, spider

1:24:53

1:27:53 没有再看了,因为感觉看不懂,目前也用不到这些高深的东西。遂直接看 14 信息检索、搜索引擎原理及应用,因为之前看过别的使用 Elasticsearch 做爬虫抓取后的搜索。

07

08

Scrapy

1
pip install scrapy

09

Python 的 PageRank - NetworkX

1
pip install networkx

查重算法
SimHash – 海明距离

依赖 sh库

[Python库]分析一个python库–sh(系统调用)

1
pip install simhash

10

Daemontool

Pillow

Pillow 是一个图像工具包,包含了一个 Image 类用来做图像的处理

1
pip install pillow

Tesseract-Ocr

Tesseract-Ocr 是一个 Google 主导的开源 OCR (Optical Character Recongnition) 引擎。

1
pip install pytesseract

12

Goose
PyGoose

1
pip install goose3

py2.7

1
pip install pygoose

13

结巴分词

1
pip install jieba

Python TF-IDF

1
pip install sklearn scipy numpy

14 信息检索、搜索引擎原理及应用

讲课开始时间:2017-07-25 19:55

06:11 开始讲课

06:11 之前的在讲爬虫

33:49 开始讲今天的内容,Elastic Search

大纲

  • 倒排索引
  • 文本匹配及排序
  • Elasticsearch

核心-倒排表

38:05

处理过程

39:13

爬虫->正文抽取->分词->Doc ID->正拍表->倒排表(词汇 ID,Term ID)

正排索引

42:40

  • LocalId:文档的局部编号
  • WordId:文档分词后的编号
  • nHits:某个索引词出现的次数
  • HitList 变长字段:某个索引词在文档出现的位置

倒排索引

44:06

  • 词典:不同索引词组成的索引表
  • 记录表:每个索引词出现过的文档集合以及命中位置

查询系统

Boolean 模型

45:23

单纯地查询每个检查词,最后把检索词的结果取交集。

词与词的关联

48:57

在提取特征词的时候,我们可以用 TfIdf 来找出特征词,同时 TfIdf 也可以用来表述一个词 w(i) 对于一篇文字 d(j) 的重要性 w(i,j) Boolean 的检索方式,词与词之间被看成是独立的,而实际上,搜索词之间的组合,是具备一定关联。

标准化网页长度

52:37

一般来说,一篇网页的文本越长,它被随机搜索词命中的机率也就越大。因此我们需要考虑把网页的长度进行标准化,一般来说,网页文本长度有以下的方式来表达:

  • Size in Bytes:网页文本流的长度
  • Size in Words:网页字词数量
  • 向量的模:

向量模型

56:00

概率模型

58:37

贝叶斯模型

几种模型的简单比较

1:12:44

Elasticsearch

1:42:16

×

纯属好玩

扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

文章目录
  1. 1. 手把手带你构建一个分布式爬虫系统实战-小象学院-学习笔记
  2. 2. 01 静态网页爬虫:爬虫的基础技术
    1. 2.1. 大纲
    2. 2.2. 基础环境 - Python
    3. 2.3. HTTP 协议
    4. 2.4. TCP/IP 四层 与 OSI 七层
    5. 2.5. HTTP 协议
      1. 2.5.1. HTTP HEADER
      2. 2.5.2. REQUEST 部分的 HTTP HEADER
      3. 2.5.3. keep-alive
      4. 2.5.4. RESPONSE 的 HTTP HEADER
      5. 2.5.5. HTTP 响应状态码
      6. 2.5.6. HTTP 响应状态码 300
      7. 2.5.7. HTTP 响应状态码 400、500
      8. 2.5.8. 错误处理
      9. 2.5.9. HTTP 请求方法
    6. 2.6. HTML 及 CSS
    7. 2.7. HTML
    8. 2.8. DOM 树
    9. 2.9. JavaScript
    10. 2.10. 宽度及深度抓取
    11. 2.11. 爬虫的抓取对象类型
    12. 2.12. Web API 数据获取
    13. 2.13. Mafengwo 的结构
    14. 2.14. 深度优先策略
    15. 2.15. 宽度优先策略
    16. 2.16. 选择哪种策略?
    17. 2.17. 不重复抓取策略
    18. 2.18. 如何记录抓取历史
    19. 2.19. MD5 函数
    20. 2.20. 提高效率
    21. 2.21. BITMAP 方式记录
    22. 2.22. BITMAP 方式记录 压缩
    23. 2.23. Bloom Filter
    24. 2.24. pybloomfilter
    25. 2.25. 如何有效记录抓取历史
    26. 2.26. 未讲内容:网站结构分析
    27. 2.27. 问题解答
  3. 3. 02 登录及动态网页的抓取
    1. 3.1. 大纲
    2. 3.2. 网站结构分析 Robots.txt
    3. 3.3. Sitemap
    4. 3.4. 分析网站结构
    5. 3.5. 有效率抓取特定内容
    6. 3.6. XPath
      1. 3.6.1. @ 属性
      2. 3.6.2. 运算符
    7. 3.7. 正则表达式
      1. 3.7.1. 常用规则
      2. 3.7.2. 爬虫常用的正则规则
      3. 3.7.3. 贪婪模式及非贪婪模式
    8. 3.8. 动态网页
      1. 3.8.1. 动态页面的使用场景
      2. 3.8.2. 动态页面带来的挑战
      3. 3.8.3. 抓取动态页面-分析
      4. 3.8.4. 分析动态网页-对比
    9. 3.9. 问题解答
    10. 3.10. Python Web 引擎
    11. 3.11. 使用 PhantomJS 来加载动态页面
      1. 3.11.1. set_window_size
      2. 3.11.2. Built-in DOM selector
      3. 3.11.3. Useful Methods & Properties
      4. 3.11.4. Close and Clear
    12. 3.12. 提取动态数据
      1. 3.12.1. 提取动态数据代码
    13. 3.13. href 的类型 \ and \\
    14. 3.14. PhantomJS 配置
    15. 3.15. 证书授信 CA SSL HTTP
    16. 3.16. PhantomJS config
    17. 3.17. 总结
    18. 3.18. 答疑
  4. 4. 03 微博的抓取
    1. 4.1. 大纲
    2. 4.2. 登录
    3. 4.3. 输入用户名与密码
    4. 4.4. 分析微博网站结构
    5. 4.5. 关注、分析
    6. 4.6. 获取微博外链
    7. 4.7. 获取微博用户信息
    8. 4.8. 微博信息抽取 - 微博正文抽取
    9. 4.9. 微博图片信息
    10. 4.10. 滚动频率与翻页
    11. 4.11. 滚屏
    12. 4.12. 微博抓取框架 - 重点
    13. 4.13. 微博接口分析
    14. 4.14. 个人首页
    15. 4.15. 个人 feed 流
    16. 4.16. 点击打开个人关注页
    17. 4.17. 爬取流程
    18. 4.18. 总结
    19. 4.19. 答疑
  5. 5. 04 多线程与多进程的爬虫
    1. 5.1. 大纲
    2. 5.2. HTML 提交数据
    3. 5.3. cookie 和 session
    4. 5.4. 后台处理流程
    5. 5.5. HTML 表单
    6. 5.6. 表单类型: form-data
    7. 5.7. 表单类型:x-www-form-urleencoded
    8. 5.8. ajax 登录
    9. 5.9. 登录
    10. 5.10. 表单登录 Python 代码
    11. 5.11. 意外??
    12. 5.12. urllib2 插件
    13. 5.13. 获取并设置 Cookie
    14. 5.14. 使用 urllib2 的插件功能
    15. 5.15. CookieJar
    16. 5.16. 多线程爬虫
    17. 5.17. 多线程的复杂性
    18. 5.18. 画个图
    19. 5.19. 多线程的优势
    20. 5.20. Python 线程
    21. 5.21. 实现一个多线程爬虫
    22. 5.22. 多线程爬虫评价
    23. 5.23. 多进程爬虫
    24. 5.24. 多进程爬虫评估
    25. 5.25. 进程间通信
    26. 5.26. Android 进程间通信 Binder
    27. 5.27. Android 进程间通信 AIDL
    28. 5.28. 创建多进程爬虫
      1. 5.28.1. Solution A - C/S 模式
      2. 5.28.2. Solution B - 数据库模式
    29. 5.29. C/S v.s. 数据库
    30. 5.30. 创建 MySQL 数据库表
    31. 5.31. 数据库创建
    32. 5.32. Python MySQL Connector
    33. 5.33. 1:48:49 课程结束
    34. 5.34. 1:48:51 下节课预热
    35. 5.35. 总结
    36. 5.36. 解答
  6. 6. 05 微博数据的存储:分布式数据库及应用
    1. 6.1. 大纲
    2. 6.2. 分布式爬虫系统
    3. 6.3. 分布式爬虫的作用
    4. 6.4. 将多进程爬虫部署到多台主机上
    5. 6.5. 分布式存储
    6. 6.6. 爬虫原始数据存储特点
    7. 6.7. Google FS
    8. 6.8. Python hdfs module
    9. 6.9. HBASE
    10. 6.10. HBASE 和 HDFS 比较
    11. 6.11. HBASE 样子
    12. 6.12. HBASE 存储架构
    13. 6.13. HBASE 框架图
    14. 6.14. 结合代码去学 HBASE 怎么使用
    15. 6.15. 存储方案
    16. 6.16. 1:07:35 休息下,答疑
    17. 6.17. MongoDB
    18. 6.18. 非关系型数据库的优势
    19. 6.19. MongoDB 安装
    20. 6.20. pymongo
    21. 6.21. 数据库类型
    22. 6.22. Redis
    23. 6.23. Redis 安装
    24. 6.24. MongoDB 的优化
    25. 6.25. Mongo with Redis ,Redis 做缓存级别的查询
    26. 6.26. 代码
    27. 6.27. 答疑
  7. 7. 06 多机并行的微博抓取:分布式系统设计
    1. 7.1. 大纲
    2. 7.2. 分布式系统
    3. 7.3. 分布式系统的优势
    4. 7.4. 分布式系统遇到的挑战
    5. 7.5. 分布式爬虫系统
    6. 7.6. Master-Slave 结构
    7. 7.7. Remote Procedure Calls
    8. 7.8. Protocol - Message Type
    9. 7.9. Protocol - Actions
    10. 7.10. Protocol - Key Definition
    11. 7.11. 爬虫线程
    12. 7.12. Socket 编程
    13. 7.13. 创建 socket client
    14. 7.14. 创建 socket server
    15. 7.15. Ways to listen
    16. 7.16. Non-blocking mode listening
    17. 7.17. 停止 ways to end communication
    18. 7.18. server socket, client socket, spider
  8. 8. 07
  9. 9. 08
  10. 10. 09
  11. 11. 10
  12. 12. 12
  13. 13. 13
  14. 14. 14 信息检索、搜索引擎原理及应用
    1. 14.1. 大纲
    2. 14.2. 核心-倒排表
    3. 14.3. 处理过程
    4. 14.4. 正排索引
    5. 14.5. 倒排索引
    6. 14.6. Boolean 模型
      1. 14.6.1. 词与词的关联
      2. 14.6.2. 标准化网页长度
    7. 14.7. 向量模型
    8. 14.8. 概率模型
    9. 14.9. 几种模型的简单比较
    10. 14.10. Elasticsearch
,