爬虫技术系统性总结

准备系统地复习一遍爬虫,填填坑。

contents

  1. 1. 一:数据库的使用(mongoDB)

  2. 2. 二:python环境的安装

  3. 3. 三:PyMongo的使用

  4. 4. 四:正则表达式的应用一

  5. 5. 五:正则表达式的应用二

  6. 6. 六:网页内容解析

  7. 7. 七:网页内容选取神器XPath法

  8. 8. 八:多线程与常见算法

  9. 9. 九:动态网页的分析

  10. 10. 十:Cookie登录与post登录

  11. 11. 十一:常见反爬虫机制与应对方法

  12. 12. 十二:突破简单的验证码

  13. 12.1. 人工打码

  14. 12.2. 图象识别

  15. 13. 十三:Scrapy使用

  16. 13.1. Scrapy使用

  17. 13.2. 创建项目

  18. 13.3. 爬虫的运行

  19. 13.4. 最后来讲讲以前遗留的问题mongodb安装为windows服务

  20. 14. 十四:Scrapy与MongoDB

  21. 14.1. Scrapy的工程结构

  22. 14.2. Scrapy与MongoDB

  23. 14.3. 爬取数据

  24. 15. 十五:分布式爬虫的模型分析与创建

  25. 16. 十六:复杂的分布式爬虫

  26. 17. 十七:维护多个Session

  27. 17.1. 分布式爬虫的登录

  28. 17.2. Scrapy与Selenium

  29. 17.3. 验证码

一:数据库的使用(mongoDB)

1.什么是MongoDB?

MongoDB 是一款开源的文档数据库,并且是业内领先的 NoSQL 数据库,用 C++ 编写而成。

2.为什么要使用MongoDB数据库

MongoDB是非关系型数据库,MongoDB数据的储存形式和Python的字典非常的相似。

如下图所示:python爬虫系统学习一:数据库的使用,mongoDB

3.MongoDB 的安装

1)百度mongodb官网选择自己系统匹配的版本下载并安装

2)创建文件夹用来存放数据文件:D:\MongoDB\DB

3)在MongoDB的安装文件夹中,按住Shift键并点击鼠标右键,选择“在此处打开命令窗口”,然后输入以下代码启动MongoDB:

mongod.exe--dbpathD:\MongoDB\DB

4.图形化图形化管理工具-RoboMongo

简介:RoboMongo是一个跨平台的MongoDB管理工具。可以用来在图形界面中观察我们对MongoDB的修改是否生效。

安装以后,我们打开可以看到下图的栏目:

点击“Create”,如果MongoDB就在本地电脑上面运行,那就什么都不需要修改,直接点Save

回到图1的界面,点击“Connect”就可以联接MongoDB了。

二:python环境的安装

python安装

这里使用python2.7版本的环境

同样直接百度python官网下载自己系统对应版本的python解释器exe格式或msi格式都可以

注意:不要下载360软件管家提供的 python2.7.12rc1解释器此解释器无法使用pip安装第三方包

安装完成之后打开命令提示符 cmd 直接输入 python 如果安装成功则会出现如下提示

如果没有出现上图的提示则检查系统环境变量

参考 http://jingyan.baidu.com/article/48206aeafdcf2a216ad6b316.html

PyMongo 的简介及安装

1.什么是 PyMongo?

PyMongo模块是Python对MongoDB操作的接口包,代码主要实现对MongoDB的几种操作:增删改查以及排序等功能。

2.PyMongo 的安装

使用以下几种方法之一即可:

(1)直接使用pip 安装(推荐)

安装命令如下:

pip install pymongo 如果安装成功会显示pymongo包的版本号

(2)使用easy_install 安装

安装命令如下:

easy_install pymongo

由于众所周知的原因,pip 直接安装可能会遇到网络问题导致安装失败,因此,对于Windows下的同学,还可以访问:

http://www.lfd.uci.edu/~gohlke/pythonlibs/

在这个网站上找到pymongo,并下载whl包到本地,然后使用以下命令安装:

注意要下载与你python版本相符的pymongo包

pip install 下载下来的whl文件名

对于一些更复杂的安装情况,请访问MongoDB官网,查看完整的安装指导:

https://api.mongodb.com/python/current/installation.html

验证安装

请打开Python的交换环境,输入以下代码并回车,如果不报错,就表示安装成功:

import pymongo

PyMongo的使用

创建数据库

1

2

3

4

5

6

7

8

9

import pymongo

client = pymongo.MongoClient() 本地数据库不需要填写参数

database = client.person_info 创建名为person_info的数据库

col = database.people 在person_info数据库中创建people表

info_dict = {‘name’: ‘kingname’, ‘age’: 0, ‘sex’: ‘unknown’, ‘salary’: 888888}

col.insert(info_dict) 在表中插入数据

kingname = col.find()

for each in kingname:

print(each[‘salary’]) 查询数据

三:PyMongo的使用

导入模块

插入

MongoDB的插入操作非常简单。用到的方法为:insert(参数) ,插入的参数就是Python的字典。

1

2

data = {‘id’: 123, ‘name’: ‘ds’, ‘age’: 20, ‘salary’: 999999}

col.insert(data)

我们做爬虫,主要用MongoDB是来储存数据。所以主要使用的就是这个insert方法。

查找

MongoDB的查找功能对应的方法是:find(参数)和find_one(参数), 两个参数的类型均为Python字典,参数可以省略。其中,find_one()一次只返回一条信息。我们一般使用的最多的是find(参数)这个方法。

可以通过参数指定需要查找的内容。其中的参数可以省略不写。

1

2

content = col.find()

content = col.find({‘age’: 29})

第一行代码会返回你指定集合中的所有的内容。

第二行代码会返回所有年龄为29岁的人的记录。

这两种方式得到的结果content是一个pymongo对象,但是可以使用for循环展开。展开以后可以得到很多个字典。每个字典对应一条记录。

1

2

3

for row in content:

id = row[‘id’]

name = row[‘name’]

更新

更新用到的方法叫做update_one(参数1, 参数2)或者update_many(参数1,参数2), 前者只更新一条信息,后者更新所有符合要求的信息。这里的参数1,和参数2都是字典,都不能省略。请看代码:

1

2

col.update_one({‘age’: 20}, {’$set’:{‘name’: ‘kingname’}})

col.update_many({‘age’: 20}, {’$set’:{‘age’: 30}})

第一行代码的作用是,将第一个年龄为20岁的人的名字改为kingname。

第二行代码的作用是,将所有年龄为20岁的人的名字全部改为kingname。

删除

删除 用到的方法叫做delete_one(参数)或者delete_many(参数)这里的参数都是字典,不建议省略参数。delete_one(参数)只删除一条记录,delete_many(参数)删除所有的符合要求的记录。

1

2

col.delete_one({‘name’: ‘kingname’})

col.delete_many({‘name’: ‘kingname’})

第一行代码,删除第一个名字叫做kingname的人。

第二行代码,删除所有名字叫做kingname的人。

总结

我们在制作爬虫的过程中可能涉及到的PyMongo的知识主要就是这些。当然PyMongo还有一些其他的方法,如果大家有兴趣的话,可以查看PyMongo的官方文档:https://docs.mongodb.com/getting-started/python/client/

ORM 介绍

ORM(Object-relational mapping,对象关系映射),可以将对数据库的操作变为对象的操作。

MongoEngine就是MongoDB的一个ORM库,我们使用MongoEngine以后,就可以通过直接操作对象来控制MongoDB。

一般MongoEngine在基于Python的网页开发中应用较多。不过,在我们的爬虫上,也可以使用。

MongoEngine 的安装

使用pip 安装:pip install mongoengine

安装效果如下:

验证安装

打开python 交互环境,输入以下代码并回车,如果没有报错,则表示安装成功:

import mongoengine

MongoEngine 的使用

1.初始化连接

如果我们的MongoDB 是直接在本地电脑上面运行的,可以使用以下代码来连接到电脑上的MongoDB数据库:

1

2

from mongoengine import *

connect(‘数据库名’)

如果MongoDB不是运行在本地电脑上面的,就需要指定ip 地址和端口:

1

2

from mongoengine import *

connect(‘数据库名’, host=‘192.168.2.12’, port=3456) #请注意端口号是数字不是字符串

2.定义文档

定义一个类,这里我们以个人信息为例。这个类继承MongoEngine 的Document类。请注意,这里的类名People 对应了MongoDB中的集合名。类中的每一个变量,对应了每一条记录中的列名。

1

2

3

4

5

6

from mongoengine import *

class People(Document):

name = StringField(required=True) #请注意所有写了required=True的变量,在类初始化的时候都是必须填写的参数哦。

age = IntField(required=True)

sex = StringField(required=True)

salary = IntField() #这里的IntField 或者StringField 对应了数据类型

3.创建对象

初始化People类,创建一个对象:

1

2

kingname = People(name=‘kingname’, age=18, sex=‘male’, salary=99999) #注意这里的参数name, age 和sex是不可以省略的,但是salary可以省略

kingname.save()

当然,我们也可以这样写:

1

2

3

kingname = People(name=‘kingname’, age=18, sex=‘male’)

kingname.salary = 99999

kingname.save()

在信息已经保存以后,如果你想修改某个信息,你可以这样写:

1

2

kingname.age = 22

kingname.save()

这样就把年龄修改为22岁了。是不是比pymongo简单太多了?

4.读取对象

如果想读取所有的用户信息怎么办呢?非常简单:

1

2

3

4

for person in People.objects:

print(person.name)

print(person.age)

print(person.sex)

按条件搜索也非常简单,在People.objects后面加参数即可,例如搜索所有年龄为22岁的人:

1

2

for person in People.objects(age=22):

print(person.name)

你甚至会怀疑我到底是不是把这些信息写入到了数据库里面。你可以用RoboMongo读取一下数据库,看看是不是有一个集合叫做People,里面有我们添加进去的数据。

四:正则表达式的应用一

正则表达式介绍:

正则表达式(Regular Expression)是一段字符串,可以用来在一段文本中,查找,替换有规律的信息。

做一个比喻,假设在你的面前有一万个人,我让你去找一个人,他有如下特点:皮肤是绿色的,身高三米,内裤套在头上。那么只要这个人在这一万人中,你就能一眼找到他。如果我让你从这一万人中找十个有这种特征的人,你也可以在一瞬间把这十个人从这一万人中找出来。这个寻找的过程,在正则表达式中,叫做“匹配”。而你的大脑,天生就具有正则表达式的功能。

在计算机中,我们需要让计算机程序从一大段文本中找到我们需要的内容。就可以使用正则表达式来帮助我们。

使用正则表达式有如下步骤:

· 寻找规律

· 使用正则符号表示规律

· 提取信息

我们来举一个例子,下面有一段话:

今天天气不错,。password:88886666:password我刚刚不小心把我的密码写了出来。你能看到我的密码吗?

我发现我们都喜欢使用同样的密码,昨天我不小心看到了小红的密码password:11112222:password于是我用这个密码去入侵她的电脑,没想到成功了。

在她的电脑中,我发现了她的银行卡密码password:33334444:password于是我把她的银行卡密码也修改了。

在这一段文字中,一共出现了三个密码。这三个密码很有规律,他们都是 password:数字:password 这种形式的。那么,如果我可以有什么办法,能把符合 password:数字:password 这种格式的内容里面的数字提取出来,我就可以直接得到密码了。这就需要使用正则表达式来完成这个工作。

常用基本符号的基本意义:

.可以代表任意除\n以外的字符

一个点号代表一个字符 就是一个占位符

星号*表示匹配星号之前的0次或多次

问号?匹配前面的0次或1次

\\转义字符 不能单独使用 让有特殊意义的字符变成普通字符:\* 代表普通星号

让普通字符变成有意义的特殊字符\d代表数字

括号的使用

() 是为了提取匹配的字符串。当我们使用正则表达式提取内容的时候,可能只想提取部分内容,于是我们需要使用括号来标记我们需要的内容。

通过下面这个例子说明:

有一个字符串

1

2

3

4

a = Pythonpachong

B = re.findall (‘Pyth…chong’,a)

Print(B) 返回变量a的所有字符Pythonpachong

B = re.findall (‘Pyth(…)chong’,a)

加上括号之后 只返回 onpa

提取数字

正则表达式里面,使用 \d 来表示一位数字。这里要强调一下,\d 虽然是由从左上向右下的斜杠和字母d构成的,但是我们要把\d看成是一个正则表达式符号整体。从左上向右下的斜杠,名字叫做“反斜杠”,在正则表达式中是作为转义字符,不能单独使用。

如果要提取两个数字,我们可以使用 \d\d,如果要提取三个数字,我们可以使用\d\d\d,但是问题来了,如果我们不知道有这个数有多少位怎么办呢?就需要使用上一课讲到的+号。+ 号可以匹配它前面的符号一次或者多次。所以使用 \d+,可以表示一个任意位数的数字。

我们的密码是123455677,请记住它。

当我们对这一段文字使用(\d+)的时候的时候,就会把123455677给提取出来。

提取文本

对于文本来说,我们在爬虫中一般使用 .*? 这三个符号来完成。

我们知道点号表示任意非换行符的字符,*号表示匹配它前面的字符零次或者任意多次。所以 .* 表示匹配一串任意长度的字符串任意次。这个时候必须在.* 的前后加其他的符号来限定范围,否则得到的结果就是原来的整个字符串。

如果在 .* 的后面加一个问号变成 .*? ,那么可以得到什么样的结果呢?问号表示匹配它前面的符号0次或者1次。于是 .*? 的意思就是,匹配一个能满足要求的最短字符串。

提取多行文本

只需把文本匹配模式中加入\n:.*?\n.*?

· .* 外号贪心算法,获取最长的满足条件的字符串

匹配字符串中所有满足条件的内容

.*? 外号非贪心算法,获取最短的能满足条件的字符串

只匹配一个满足条件的内容后退出

五:正则表达式的应用二

导入模块

Python的正则表达式模块名字为 re ,我们可以使用:import re来导入并使用。

findall 方法

Python的正则表达式模块包含一个findall方法,它可以以列表的形式返回所有满足要求的字符串

findall的函数原型为:

re.findall(正则规则, 内容, [flag=0])

findall的结果是一个列表,包含了所有的匹配到的结果

当我们需要提取某些内容的时候,需要使用小括号将这些内容括起来,这样才不会得到不相干的信息。如前文所说的一样

函数原型中,最后一个参数是可以省略的。当不省略的时候,可以起到一些辅助功能。例如忽略大小写,忽略换行符等等。我们这里以忽略换行符为例来进行说明,请看下图:

当我们抓取网页的时候,非常容易出现这样的情况,我们要匹配的内容中存在换行符\n,这种情况下,要忽略换行符,就需要使用到re.S这个flag. 我们对比上图的结果,就会发现flags的作用。虽然说匹配到的结果中出现了\n 这个符号,不过我们后期清洗数据的时候把它替换掉即可。

search 的使用

search方法可以返回第一个满足要求的字符串。一旦找到符合要求的内容,它就会停止查找。

search的函数原型为:

re.search(正则, 内容, flags=0)

search的结果是一个正则表达式的对象,需要通过.group()这个方法来获取里面的值。

content = re.search('i(.*?)nfo">(.*?)<', example).group()

group(X)括号里面的参数为数字值的师模式中括号的个数,X填多少则返回第几个括号里的内容

到这里正则表达式我们常用的方法和字符都学完了。如果要想知道python正则模块re模块都有哪些方法怎么用,可以直接打开库文件re.py查看里面的注释就可以了,当然里面注释全部是英文,自己复制出去用翻译软件翻译过来就可以了

大家可以在Python安装文件夹下面的Lib文件夹里面找到它,例如:

c:\python27\Lib\re.py

大家有空就多看看吧!

六:网页内容解析

Requests的介绍和安装

requests是Python的一个第三方Http库,它比Python自带的urllib更加的简单方便和人性化。使用requests可以让Python实现访问网页并获取源代码。

使用requests获取网页的源代码,最简单的情况下只需要2行代码:

1

2

import requests

source = requests.get(‘http://www.jikexueyuan.com’).content

安装

cmd命令行窗口下使用pip 安装requests:pip install requests

安装成功后提示:

pip 在线安装有可能会受到防火墙的干扰,因此也可以使用源代码安装。打开网页https://github.com/kennethreitz/requests, 点击Clone or download->Download ZIP下载源代码,如图:

解压源代码,找到setup.py, 并打开CMD窗口或者终端,执行以下代码:

python setup.py install

或者在 http://www.lfd.uci.edu/~gohlke/pythonlibs/ 下载编译好的安装包

直接在对应目录 按shift+鼠标右键选择在此处打开命令窗口

输入pip install 安装包全名带后缀安装即可

requests不依赖于其他第三方Python库,因此你还可以直接从其他安装了requests的电脑中,从Python安装文件夹下面的Lib/site-pacakges文件夹中复制requests文件夹,并放在你自己的Python安装文件夹下面的Lib/site-packages文件夹下。你甚至可以不用放在site-packages文件夹下,直接放在你的工程文件夹下面也没有问题。

get 与 post 方法使用

对于大多数直接在浏览器输入网址就能访问的网页,使用requests的get方法就能获取到网页的源代码:import requests

请注意,这里需要使用.content来显示网页的源代码。如果不加.content, 则得到的只会是网页访问的状态码,类似于下面这样:

<Response [200]>

对于不能直接在浏览器中输入网址访问的页面,就需要使用requests的post方法来获取源代码。

post方法的格式如下:import requests

其中data这个字典的内容和项数需要根据实际情况做修改,key和value在不同的网站,是肯定不一样的。而我们做爬虫,构造这个字典是任务之一。

还有一些网址,提交的内容需要是json格式,因此我们的post方法的参数可以做一些修改:

html = requests.post('网址', json=data).content

这样写以后,requests可以自动将我们的字典转换为json字符串。

post提交常用于表单的提交,与爬虫伪装。

七:网页内容选取神器XPath法

XPath的介绍

在学习了正则表达式和requests以后,你已经可以写出一些简单的爬虫了。但是当你写的爬虫越多,你越会发现正则表达式非常的反人类,经常会出现不明原因的无法提取出想要的内容。最后即便绞尽脑汁终于把想要的内容提取了出来,却发现浪费了太多的时间。

正如我们在正则表达式开始时候举的例子,有一个人,身高3米,皮肤是绿色的,内裤套在头上。哪怕你面前有一万人,你也可以一眼招到他。现在问题来了,我告诉你,这个人确实存在,但是他是在地球上的某个地方,你去给我把他找出来。

这不是一个轻松的工作了。因为地球那么大,即便你一眼能看到一万人,你也可能要花很多年的时间,走遍地球的每个角落,才能找到你需要找的人。

但是如果我告诉你,这个人在中国,北京,海淀区,五道口,xx大厦xx楼xx号工位。你就可以马上买张飞机票过去找人了。XPath就是这样一种根据地址找人的技术。

XPath(XML Path Language)是一种HTML和XML的查询语言,他能在XML和HTML的树状结构中寻找节点。在Python中,我们安装lxml库来使用XPath 技术。

xml的安装

Windows系统安装lxml需注意由于lxml的底层是使用C语言实现的,因此你需要确保电脑上面需要安装Virtual C++运行库,然后使用pip安装就可以。如果你确认电脑上面已经安装了Virtual C++运行库还是无法正确安装lxml。就请访问http://www.lfd.uci.edu/~gohlke/pythonlibs/#lxml 上下载自己系统对应的安装包 如果你的电脑是Windows 32位,Python 版本是 3.5 的话,那么下载:lxml-3.6.0-cp35-cp35m-win32.whl最新版

下载完成以后,在存放这个whl包的文件夹中打开cmd, 并执行以下代码:

pip install lxml-3.6.0-cp35-cp35m-win32.whl

cmd的打开方法参照上一章即可

如果这个方法还不能安装那么使用下面这个方法直接复制过去

把lxml-3.6.0-cp35-cp35m-win32.whl这个文件的后缀名由.whl改为.zip,使用WinRAR或者7-Zip等解压缩工具解压。解压以后,你会得到两个文件夹,如图:

把他们复制到Python安装文件夹下面的 Lib\site-packages 文件夹下。然后,你的lxml就安装好了。这种方式对OS X和Ubuntu同样适用。

同样安装完成之后验证一下

打开Python的交互环境,输入:import lxml

XPath的语法讲解

1.HTML树状结构

首先我们来看一下如下的HTML倒立的树形结构:

html是树根,从树根上面分出了两个树枝head和body, body下面又分出了class分别为useful和useless的两个树枝……因此,根据每个树枝独特的标志,一步一步找下去,就可以找到特定的信息。

2.XPath语法

从上面那段代码中我们需要并且只需要提取出:

我需要的信息1

大家可以考虑一下,如果使用正则表达式应该怎么写代码。

如果我们使用XPath,关键代码只有1行:

infoList = selector.xpath('//div[@class="useful"]/ul/li/text()')

是不是比正则表达式简单太多了呢?

使用XPath的流程如下:

from lxml import html

其中的网页源代码我们可以使用requests来获取。一段XPath代码可以按照如下的规则来构造。

3.XPath语句格式

获取文本:

1

2

//标签1[@标签1=“属性值1”]/标签2[@属性2=“属性值2”]/…/text()

//div[@class=“useful”]…/text()

获取属性值:

//标签1[@属性1="属性值1"]/标签2[@属性2="属性值2"]/..../@属性n

其中,[@属性="属性值"]这个不是必需的,它的作用是帮助过滤相同的标签。在不需要过滤相同标签的情况下,可以省略。

标签1的选取

标签1可以直接从html这个最外层的标签开始,一层一层往下找,这个时候,我们的XPath语句是这样的:

/html/body/div[@class="useful"]/ul/li/text()

当从html开头的时候,它前面是单斜线。这样写虽然确实也可以达到目的,但是既然不是必要的,那何必多此一举呢?那么,如何确定我们应该从哪个标签开头呢?

回到这节课开始的那个例子,我们要按照地址寻找人。我可以说,“中国,北京,海淀区,五道口,xx大厦xx楼xx号工位”。但是,如果全中国只有一个五道口,所以我可以直接说:“五道口,xx大厦xx楼xx号工位”。同样的道理,我发现在这一段源代码中,我需要的内容全部都在 <div class="useful"> 这个标签的下面,而这段代码里面没有其他地方出现了这个标签,所以我就可以从这个标签开始来定位。

有同学问,能不能使用 <li class="info"> 来定位呢?答案是不能,因为在代码里,虽然我们需要的内容是使用这个标签包起来的,但是我们不需要的内容也是使用这个代码包起来的。因此,如果我们使用这个标签开始,就会导致需要的内容和不需要的内容混在一起。

好了写一个爬虫所需要的最基本的东西已经讲了完了咱们来理一理都有些什么呢:

第一步下载网页 (使用Requests)

第二部选取我们需要的内容(正则re或XPath)

第三部储存内容(MongoDB数据库)

八:多线程与常见算法

多线程介绍

我们之前所讲到的爬虫,都只有一个进程一个线程,我们称之为单线程爬虫。单线程爬虫每次只访问一个页面,不能充分利用电脑的网络带宽。一个页面最多也就几百KB,所以爬虫在爬取一个页面的时候,多出来的网速就浪费掉了。

而如果我们可以让爬虫同时访问10个页面,就相当于我们的爬取速度提高了10倍。这个时候就需要使用多线程技术了。

这里有一点要强调一下,Python这门语言在设计上的时候,有一个GIL锁。这个东西让Python的多线程都是伪多线程。本质上还是只有一个线程,但是这个线程每个事情只做几毫秒,做完几秒救保存线程,换做其他事情几毫秒,换一轮下来继续回到第一件事上,恢复线程再做几秒,继续换……

在《X战警-天启》中,万磁王他儿子从教授的学院里面救出了非常多的人。由于他速度非常的快,所以虽然它是一个人一个人救的,但是对其他人来说,就感觉像是全部同时移动到了学校外面去了一样。

这样微观上的单线程,在宏观上看起来就像是同时在做几件事。这种机制在IO密集型的操作上面影响也不大,但是在CPU计算密集型的操作上面,由于只能使用CPU的一个核,这就会对性能产生非常大的影响。所以涉及到计算密集型的程序,就需要使用多进程,Python的多进程不受GIL锁的影响。

爬虫属于IO密集型的程序,所以使用多线程不会不会对性能造成太大的影响。

multiprocessing库

multiprocessing是Python的一个多进程库,它可以实现多进程的操作。但是由于进程与进程之间不能直接共享资源,而且启动新的进程开销也比线程大得多。因此我们使用多线程来爬取。multiprocessing下面有一个dummy模块,它可以让Python的线程使用multiprocessing各种方法。

dummy下面有一个Pool类,它用来实现线程池。这个线程池有一个map 方法。这个方法可以让线程池里面的所有线程都“同时”执行一个函数。

1

2

3

from multiprocessing.dummy import Pool

pool = Pool(10)

results = pool.map(爬取函数, 网址列表)

下面来一段简单的单线程和多线程爬虫的代码,代码写的不好!只是一个思路的简单演示

单线程

多线程

爬虫的常见算法

深度优先算法

为了解释什么是深度优先算法和广度优先算法,我们举一个例子。

大家都打过RPG游戏吧,在游戏里面我们会找NPC来接任务。有的同学喜欢一次只领取一个任务,把这个任务做完,再去领下一个任务,这就叫做深度优先算法。另外一些同学他们喜欢先把所有的任务一次性接玩,然后再去慢慢完成,最后再一次性的把任务奖励都领取了。这就叫做广度优先算法。

在深度优先算法的情况下,我们的爬取路线如下图所示:

首先要走完红色线,把爬虫的所有课时都爬取完成,再走绿色线爬取Django的所有课时,最后再走黄色线爬取机器学习的的所有课时。然后再去爬取Node.js的所有信息……

广度优先算法

在广度优先的算法情况下,我们的爬取路线如下图所示:

首先走红色,绿色,肉色线爬取每个大分类的信息。然后再走橙色,蓝色,灰色线从第一个大分类中,爬取所有的课程信息,爬完了第一个大分类,再爬第二个大分类。直到所有大分类下面的课程信息都搞定了,再爬第一个课程的所有课时信息……

好了到这里大家就可以自己动手写一个爬取静态网页的小爬虫了 ,大家动起手来吧!

九:动态网页的分析

Ajax技术介绍

AJAX 是 Asynchronous JavaScript And XML 的首字母缩写,意为:异步JavaScript与XML。 使用Ajax技术,可以在不刷新网页的情况下,更新网页数据。使用Ajax技术的网页,一般会使用HTML编写网页的框架,在打开网页的时候,首先加载的是这个框架。剩下的部分或者全部内容,将会在框架加载完成以后再通过JavaScript在后台加载。

如何判断一个网页使用了Ajax技术呢?方法很简单:如果你发现你用浏览器能直接看到的内容,在网页源代码里面却搜索不到,就说明网页使用了Ajax技术,如下图所示:

JSON介绍与应用

JSON的全称是JavaScript Object Notation,它是一种轻量级的数据交换格式。 我们在网络之间传递数据的时候,绝大多数情况下都是传递的字符串。因此,当我们需要把Python里面数据发送给网页或者其他的编程语言的时候,可以先将Python的数据转化为JSON格式的字符串,然后将字符串传递给其他的语言,其他语言再将JSON格式的字符串转化为它自己的数据格式。来看这个教程的同学都是有Python基础的同学。所以你们对Python的字典肯定不陌生。我们来定义一个字典:

如果将这个字典转化为JSON格式的字符串,你将得到:

简直和Python的字典一模一样有没有?而且你还可以添加缩进,让它变得更像是字典:

但是实际上,它只是字符串。

分析使用Ajax的网站源代码

使用Ajax技术的网站,被动态加载的内容在源代码中找不到。对于这种情况,应该如何抓取她的内容呢?

我们可以使用浏览器带的开发者工具的元素审查中Network选项来查看后面加载的内容

我们需要从这里面找出被动态加载出来的内容的url

比如优酷的视频评论从上面的刷新的内容我们找出评论文件鼠标右键

选择Open link daaress 复制文件的URL地址并在浏览器中直接打开得到下图为评论文件

具体网站具体分析

爬取使用Ajax的网站

既然已经知道了在哪里可以找到被动态加载的内容,那么我们就可以构造Url来让requests访问,从而抓取被加载的内容。而且被加载的大多以JSON格式保存,非常方便我们处理并获取内容。

其中URL地址分析方法以后专门抽出一章来分析一个网站的内容,但每个网站的除了方法都不一样这就需要大家多多分析,只要找到我需要的URL地址就号处理了

使用 Selenium 翻译加密代码

1.Selenium 介绍

有时候,网站动态加载的内容经过加密后,我们是无法看懂密文的,但是JavaScript可以看懂,所以如果能一行一行读JavaScript代码,理论上可以读取任何动态加载的页面内容。但问题是大型网站的JavaScript动辄上万行,还经过混淆,肉眼去读几乎不可完成。

大家会发现,在Chrome的“检查”中,Elements选项卡下,我们可以看到被动态加载的内容,说明Chrome检查此时显示的内容,已经是被解析了以后的内容。如果我们能够获得这个被解析的内容,那就能获取到被加密的内容。

这种情况下,我们就需要使用Selenium来模拟浏览器解析JavaScript,我们再爬取被解析以后的代码。

2.Selenium安装

pip安装

pip install selenium

Selenium的使用

1.初始化webdriver

另外需要特别注意斜杠的问题,在Windows下面路径中的反斜杠需要使用特殊处理,例如:

注意这里的“r”不能少。r代表不对此处后面的字符串做特殊处理 比如当遇到\n \d之类的直接当成两个字符\\n

2.等待信息出现

由于被动态加载的内容会延迟出现,因此我们需要等待它出现以后才开始抓取。需要使用到WebDriverWait,By 和 expected_conditions

WebDriverWait会阻塞程序的运行,并每0.5秒检查一次网页源代码,看我们需要的内容是否已经出现。如果没有出现就继续等待。

在上面的代码中,设定了超时时间为300秒。在300秒内,如果有某个元素出现,那么就解除阻塞,继续运行后面的代码;如果等待的内容始终不出现,那么就会抛出一个超时的Exception。

我们来看一下:

这里的EC其实就是

也就是期望的条件。Python接近英语的语法让我们可以非常轻松的看懂这一段代码:

期望的条件.元素出现

而这里的元素就是一个class="con"的元素。

这里除了指定class以外,还可以指定很多其他的属性,例如:

通过元组的形式传递给presence_of_element_located方法。

在网页中寻找我们需要的内容可以使用XPath:

更多Selenium的使用方法,可以参阅它的Python文档:http://selenium-python.readthedocs.io/getting-started.html

十:Cookie登录与post登录

到目前为止,我们接触过的网页都是不需要登录的。如果我们使用requests访问需要登录的页面会怎么样呢?我们用知乎来说明。

当我登录的时候访问知乎,看到的首页是这样的:

但是当我没有登录的时候,访问知乎首页,看到的是这样的:

而如果我们使用下面的代码访问知乎:

我们会发现得到的源代码是知乎登录页面的的源代码。对于这样的网站,我们需要让爬虫登录网站才能爬取信息。我们本课将要讲解使用Python模拟登录网站。

使用Selenium模拟登录知乎

使用Selenium来进行模拟登录,整个过程非常的简单,只需要输入账号,输入密码,然后登录即可。流程如下:

1.初始化chromedriver

2.打开知乎登录页面

3.找到用户名的输入框,输入用户名

4.找到密码输入框,输入用户名

5.按下回车键

以上过程,我们使用Selenium, 在一般情况下只需要不到20行代码。

为什么我要强调一般情况呢?那是因为知乎实际上也是有验证码的,只是在一般情况下不需要输入验证码。如果你的IP地址异常,或者你反复多次登录又退出,知乎会认为你的账号异常,这种情况下就会出现验证码。

关于验证码的识别,我们在后面的课程将会讲到。我会在视频中为大家演示不需要输入验证码的情况下模拟登录知乎,大家自己在测试的时候,如果你发现你登录知乎需要输入验证码,请换一个其他网站来进行测试。

借助Chrome直接获取到Cookie

使用Cookie登录首先要获取Cookie,那么我们怎样获取Cookie呢?

其实我们可以借助Chrome直接获取到Cookie,不过这种方式有局限性,并不如Fiddler通用,有一些网站使用这种方式获取的Cookie无法登录。

经过测试,这种方式对知乎有效。

在已经登录知乎的情况下,打开开发者工具,定位到 Network 选显卡,然后刷新网页,在加载的内容中,随便选择一项,然后看右侧的数据,如下图所示:

在右侧的Request Headers下面,你可以找到Cookie这一项,它后面的一长串数据就是我们需要的Cookie。

请注意这里一定要是”Request Headers”,不要选成了”Response Headers”。

使用这种方式获取的Cookie比使用Fiddler获取的Cookie要长几十个字符,不过不影响正常使用。以下是获取知乎回答的一个例子:

源代码中出现了“私信”和“我的首页”这两链接,说明是以登录的身份获取的源代码。

使用Fidd获取Cookie

Fiddler是Windows下的一个网络调试工具,可以在浏览器和网站服务器之间建立一个代理。浏览器和网站之间的所有数据交换都会经过Fiddler,因此使用Fiddler可以对网络数据进行抓包,从而分析数据的格式和内容。

大家可以在Fiddler的官网下载到它:https://www.telerik.com/download/fiddler。安装过程非常简单,一路Next即可

安装完成后第一次运行,它可能会弹出一些警告,不需要理会。点“Cancel” 或者“No” 。打开主界面以后,先别忙动,把IE 或者Edge浏览器打开。这里不建议使用Chrome,因为在Fiddler中监控Chrome有点问题。

如果你之前已经打开了浏览器,请把多余的标签页关掉,否则数据量太大会把你需要的信息冲掉。

回到Fiddler上,点击左上角的WinConfig,在弹出来的窗口中选择Microsoft Edge,如图所示:

如果你使用的是IE,那就请勾选:windows_ie_ac开头的那一项。

关闭这个窗口并回到浏览器中,登录你需要访问的那个网站。此时在Fiddler中会看到很多数据,从下往上找到你需要的那个网站。在右侧,打开Composer选项卡,然后将左侧的网址直接拖到右边来。

此时可以看到右侧出现了一些数据。我们需要的数据是“Cookie”这一行。

Cookie是一小段文本,保存在我们本地。之所以很多网站我们只需要登录一次,下次就可以直接进入,正是归功于这一小段文本。它里面保存了我们的登录信息,并且按照一定的算法加密。网站可以拿到这一段文本并解密,从而判断是否需要让我们重新登录。

使用Cookies登录

我们现在已经有这一段“登录后”的Cookie了,所以如果我们的脚本访问网站的时候,把这段代码一并提交上去,就可以让网站以为我们已经登录过,于是直接显示登录后的内容。

使用Cookie来登录网页,有时候可以直接绕过网站的验证码。

借助requests的Session模块,我们可以用几行代码加Cookie来实现登录知乎。

获取网页的时候,网址可以随便修改,只要保证每次都有cookies=cookie这个参数,就可以正常访问登录以后才能访问的页面。

用 post 方法登陆知乎

在之前的章节中,我们主要使用了requests的get方法来直接获取网页的内容。支持GET方法的网页,接受我们的URL, 从URL里面取出参数,然后再将结果返回给浏览器。由于URL的长度是有限制的,所以只能接收少量的参数。而支持POST方法的网页,可以传递大量的参数和大量的信息。如果一个网页只支持POST方法,那这个网页是没有办法在浏览器里面直接输入网址访问的。requests的post方法支持将参数以POST方式提交给网页,并得到网页的返回信息。

大多数的网站,登录的用户名和密码都是通过POST方式提交给服务器的,因此如果可以模拟网站的这个提交行为,就可以模拟登录网站。

我们首先进入知乎的登录页面,打开Chrome的开发者工具的Network选项卡,勾选“Preserve log”并故意输入错误的账号和密码尝试登录,从Chrome抓取到的数据中我们可以找到我们需要的信息:

从以上两张图中,我们可以看到,当我们点击了登录按钮以后,程序会将图二红框中的Form Data下面四个数据使用POST方式发送给图一红框中的https://www.zhihu.com/login/email 这个URL。

注意:如果你输入的账号不是手机号,而是邮箱,数据将会发送到的URL为:https://www.zhihu.com/login/phone_num

上图中的Form Data,后三项的意义显而易见,email: 邮箱, password: 密码, remember_me: 记住我。但是第一项”_xsrf”是什么意思?它后面的一长串数据是哪里来的?

这个时候大家打开登录页面的源代码,你将会找到这样内容:

那么事情就简单了,使用正则表达式或者XPath先从源代码里面把这个xsrf提取出来,然后再和登录信息一起提交即可。

还有一点需要注意:在Form Data中,remember_me后面的值是true, 首字母是小写的。但是在Python里面的布尔值True, 首字母需要大写。

在一般情况下是不需要使用验证码登录的,所以只需要提供图二列出的信息,将它们写成一个字典并POST提交就能实现登录。那么如果遇到验证码怎么办呢?后面我们会有专门的章节将验证码的处理。

现在,我们已经知道了登录知乎的原理,于是就可以通过代码来实现登录。核心代码如下:

其运行效果如下图所示:

网址返回的登录结果是登录成功,并且查看源代码可以证明是登录成功。

对于其他的网站,整个流程是完全一样的,只是POST提交的参数各有不同。有些网址只需要POST提交用户名和密码就可以了,而另外一些网址,可能还需要从源代码里面提取更多的参数。

十一:常见反爬虫机制与应对方法

数据头User-Agent反爬虫机制解析

我们小时候都听过一首儿歌。我说一个开头,大家肯定能把剩下的几句背出来:小兔子乖乖,把门打开…

当我们使用浏览器访问网站的时候,浏览器会发送一小段信息给网站,我们称为Request Headers,在这个头部信息里面包含了本次访问的一些信息,例如编码方式,当前地址,将要访问的地址等等。这些信息一般来说是不必要的,但是现在很多网站会把这些信息利用起来。其中最常被用到的一个信息,叫做“User-Agent”。网站可以通过User-Agent来判断用户是使用什么浏览器访问。不同浏览器的User-Agent是不一样的,但都有遵循一定的规则。

例如,我们在windows上面的Chrome浏览器,它的User-Agent是:

但是如果我们使用Python的Requests直接访问网站,除了网址不提供其他的信息,那么网站收到的User-Agent是空。这个时候网站就知道我们不是使用浏览器访问的,于是它就可以拒绝我们的访问。

例如

没有User-Agent的情况

有User-Agent的情况

如何获取网站的 User-Agent 呢?请打开 Chrome,任意打开一个网站,然后右键,“检查” 打开开发者工具,定位到 “Network” 选项卡,并刷新网页,如下图所示:

在左下角会出现当前网页加载的所有元素。随便点一个元素,于是在右下角会出现对当前元素的请求信息。在里面找到Request Headers这一项,里面的内容即为我们需要的内容。

不同的网站,Request Headers 是不同的

提示:requests的 get方法,post方法,Session模块的get方法,post方法,都支持自定义Headers,参数名为headers, 它可以接收字典作为参数。

我们可以通过字典来设定Headers,例如:

在 requests 中,使用如下代码来提交 Headers:

或者Session模块:

检查User-Agent是一种最简单的反爬虫机制,而通过设定Request Headers中的User-Agent,可以突破这种机制。

访问频率限制

我们访问一些网站,由于一些网站的访问时间设置,我们是不能快速进入下一页的,这个时候,我们需要修改他的访问时间,在它访问下一页的时候,通过 POST 方式,修改 read_time,正常访问的话,这个值一般会大于10如果我们修改成 60。于是轻轻松松的通过了它的反爬虫策略。

上面这种情况我们现在已经极少会遇到了,因为这种办法根本没有任何的防御能力。猜测这个策略可能是某个新人写的。

修改访问频率:

大多数情况下,我们遇到的是访问频率限制。如果你访问太快了,网站就会认为你不是一个人。这种情况下需要设定好频率的阈值,否则有可能误伤。如果大家考过托福,或者在12306上面买过火车票,你应该会有这样的体会,有时候即便你是真的用手在操作页面,但是因为你鼠标点得太快了,它都会提示你: “操作频率太快…”。

遇到这种网页,最直接的办法是限制访问时间。例如每隔5秒钟访问一次页面。但是如果遇到聪明一点的网站,它检测到你的访问时间,这个人访问了几十个页面,但是每次访问都刚好5秒钟,人怎么可能做到这么准确的时间间隔?肯定是爬虫,被封也是理所当然的!所以访问时间间隔你可以设定为一个随机值,例如0到10之间的随机秒数。

当然,如果遇到限制访问频率的网站,我们使用Selenium来访问就显得比较有优势了,因为Selenium这个东西打开一个页面本身就需要一定的时间,所以我们因祸得福,它的效率低下反而让我们绕过了频率检查的反爬虫机制。而且Selenium还可以帮我们渲染网页的JavaScript,省去了人工分析JavaScript源代码的麻烦,可谓一举两得。

代理IP或者分布式爬虫

如果对页的爬虫的效率有要求,那就不能通过设定访问时间间隔的方法来绕过频率检查了。

代理IP访问可以解决这个问题。如果用100个代理IP访问100个页面,可以给网站造成一种有100个人,每个人访问了1页的错觉。这样自然而然就不会限制你的访问了。

代理IP经常会出现不稳定的情况。你随便搜一个“免费代理”,会出现很多网站,每个网站也会给你很多的代理IP,但实际上,真正可用的代理IP并不多。你需要维护一个可用的代理IP池,但是一个免费的代理IP,也许在你测试的时候是可以使用的,但是几分钟以后就失效了。使用免费代理IP是已经费时费力,而且很考验你运气的事情。

大家可以使用http://icanhazip.com/ 这个网站来检测你的代理IP是否设定成功。当你直接使用浏览器访问这个网站的时候,它会返回你的IP地址。如下图所示:

通过requests,我们可以设置代理访问网站,在requests的get方法中,有一个proxies参数,它接收的数据是一个字典,在这个字典中我们可以设置代理。

大家可以在requests的官方中文文档中看到关于设置代理的更多信息:http://docs.python-requests.org/zh_CN/latest/user/advanced.html#proxies

我选择第一个HTTP类型的代理来给大家做测试,运行效果如下图所示:

从上图可以看出,我们成功通过了代理IP来访问网站。

我们还可以使用分布式爬虫。分布式爬虫会部署在多台服务器上,每个服务器上的爬虫统一从一个地方拿网址。这样平均下来每个服务器访问网站的频率也就降低了。由于服务器是掌握在我们手上的,因此实现的爬虫会更加的稳定和高效。这也是我们这个课程最后要实现的目标。

蜜罐技术

蜜罐这个词,最早是来自于网络攻防中。一方会故意设置一个或者几个服务器,故意留下漏洞,让另一方轻易的入侵进来。这些被故意设置的服务器,就叫做蜜罐。里面可能安装了监控软件,用来监控入侵者。同时,蜜罐还可以拖延入侵者的时间。

在反爬虫的机制中,也有一种蜜罐技术。网页上会故意留下一些人类看不到或者绝对不会点击的链接。由于爬虫会从源代码中获取内容,所以爬虫可能会访问这样的链接。这个时候,只要网站发现了有IP访问这个链接,立刻永久封禁该IP + User-Agent + Mac地址等等可以用于识别访问者身份的所有信息。这个时候,访问者即便是把IP换了,也没有办法访问这个网站了。给爬虫造成了非常大的访问障碍。

不过幸运的是,定向爬虫的爬行轨迹是由我们来决定的,爬虫会访问哪些网址我们都是知道的。因此即使网站有蜜罐,定向爬虫也不一定会中招。

其他反爬虫机制

有一些网站,他们每个相同类型的页面的源代码格式都不一样,我们必需要针对每一个页面写XPath或者正则表达式,这种情况就比较棘手了。如果我们需要的内容只是文本,那还好说,直接把所有HTML标签去掉就可以了。可是如果我们还需要里面的链接等等内容,那就只有做苦力一页一页的去看了。

这种网站很少,因为如果每个页面的源代码格式都不一样,要不就用户体验查,要不就做网页的内容会被累个半死。

我们之前讲到的登录验证和我们下一章将要讲到的验证码也是反爬虫策略。目前我们已经解决了登录的问题,下一章我们将会解决验证码的问题。

十二:突破简单的验证码

人工打码

人工打码又分两种一是登录后获取Cookie来登录,或者每次手动输入验证码

使用 Cookie 来登陆

在模拟登录中,我们讲到过Cookie,通过Cookie能让我们绕过登录,直接访问需要登录的网站。因此,对于需要输入验证码才能进行登录的网站,我们可以先人工登录,并通过Fiddler或者Chrome获取Cookie,然后使用Cookie来访问网站。这样就可以人工输入一次验证码,然后很长时间不再登录。

有一些网站的验证码是通过点击,或者拖动滑块来验证。这种网站目前最简单的办法就是使用Cookie来登录,其他方式都不好用。

手动输入验证码

在模拟登录这一课的作业中,我让大家登录豆瓣,并尝试通过豆瓣的验证码。作业提示中,我说到可以先把验证码下载下来,然后手动输入。这就是一般的验证码识别方法。

手动输入验证码的一般流程如下:

1.随机提交数据,获取POST的数据内容和格式

2.分析网页源代码,获取验证码地址

3.下载验证码到本地

4.打开验证码,人眼读取内容

5.构造POST的数据,填入验证码

6.POST提交

图象识别

Python的强大,在于它有非常多的第三方库。对于验证码识别,Python也已经有了现成的库来供我们使用了。开源的OCR库pytesseract配合tesseract,可以用来将图片中的文字转化为文本。

不过这种方式我们在爬虫中用的并不多。因为现在大部分的验证码都加上了干扰的纹理,已经很少能用单机版的图片识别方式来识别了。所以我们这里仅仅使用简单地图片来进行介绍。如果有一天你的运气足够好,遇到了非常工整的验证码,那么你可以使用这个办法来试一试。

pip安装

pip install tesseract

要使用tesseract来做图像识别,我们还需要安装两个库:

tesseract的使用

tesseract的使用非常简单,流程如下:

1.导入相关的第三方库

2.打开图片

3.识别

我们可以通过以下代码来实现最简单的图片识别:

运行结果如下:

打码网站的使用

打码网站的介绍:

现在还有一种在线验证码识别的网站。他们雇佣了很多失业者和残疾人来人肉识别验证码,一般十个验证码一分钱左右。我们可以使用这种网站提供的接口来帮助我们识别验证码。这种方式理论上可以识别任何使用输入方式来验证的验证码。

这种打码网站的流程一般是这样的:

1.将验证码上传到网站服务器

2.网站服务器将验证码分发给打码工人

3.打码工人肉眼识别验证码并上传结果

4.网站将结果返回给我们

整个流程走完,在网速良好的白天一般在3-4秒钟,在晚上大概8-10秒钟。

使用在线打码

大家在百度上面搜索:“验证码在线识别”,就可以找到很多提供在线打码的网站。但是由于一般这种打码网站是需要交费才能使用的,所以请一定要注意财产安全!

在这堂课里面,我使用一个叫做云打码的网站来进行测试。网站地址为:http://www.yundama.com/。注册账号以后,需要交费购买“题分”才能正常使用。大家充1元钱就可以了。千万别多充,因为这个网站看起来快要倒闭了,很多文档都没有更新,他们给出的API也有些问题。

这个网站虽然提供了各种语言的SDK,但是版本都比较旧了,所以我推荐大家使用他的HTTP接口:http://www.yundama.com/download/YDMHttp.html

这个页面上给出了上传的地址。大家不要去看它提供的“PythonHTTP调用示例”,因为这个示例有问题。

经过我的研究,它的HTTP接口的正确使用步骤如下:

1.使用POST方式上传图片,并接收返回信息。上传的网址为:http://api.yundama.com/api.php?method=upload 上传参数请看下面的示例。

2.运气和网速足够好的时候,立刻就可以从返回的信息中得到验证码。如果返回的数据没有验证码,转步骤3.

3.获取cid, 并使用GET方式反复访问http://api.yundama.com/api.php?cid=在这里输入你获取到的cid&method=result 直到获取到验证码为止

我这里给出一个正确的使用方式的代码片段:

至于之前有人问滑动验证码怎么破,今天实验了一下,无非两种方法 模拟鼠标动作

但每个网站的滑动方式又不一样有的滑动到底有滴滑动到指定位置 还不如使用Cookie方式登录好用,不过这种方式要定期跟换。

十三:Scrapy使用

Scrapy使用

Scrapy是基于Python的分布式爬虫框架,使用它可以非常方便地实现分布式爬虫。Scrapy高度灵活可定制,让你的写出的爬虫可以应对各种网站情况。同时Scrapy封装了很多爬虫的实现细节,所以可以把更多的精力放在数据的提取上。

这一章,将会在Python 2 环境下安装和使用Scrapy,有人推举直接安装Anaconda他包含了所有的scrapy组件,但他也包含了我们用不到的组件所以还是直接安装scrapy!

在安装scrapy之前 先要安装一个VC++8.0 应为后面安装scrapy的其中一个依赖库必须要使用它来编译,直接在微软官网上下载安装就ok

https://www.microsoft.com/en-us/download/details.aspx?id=44266

安装pywin32,在下面的连接中下载最新版的pywin32

http://sourceforge.net/projects/pywin32/files/pywin32/

接着安装lxml,直接使用命令pip install lxml来安装

然后就可以运行命令:pip install scrapy来安装scrapy了,pip会自动安装Scrapy所需要的其他第三方库。Scrapy所需要依赖的第三方库非常多,所以会花比较长的时间才能安装完成。如图所示为安装Scrapy的时候,自动安装的依赖(部分):

创建项目

然后再cmd命令行进到你源代码存放的目录运行

scrapy startproject tutorial 来创建scrapy项目

上面的“tutorial”为工程名,可以使用大部分的英文单词或者词组或者字母加数字。但是工程名绝对不能为”scrapy”或者Python的第三方库的名字。否则会导致Python的包导入问题。

打开刚刚创建好的工程,可以看到如下图所示的目录结构:

然后就可以在spiders目录下创建我们的爬虫文件了

在图中也可以看到我之前创建的example.py文件的内容。请注意第6行:

这就是告诉Scrapy, 这个爬虫的名字叫做”example”。

现在,只修改一行内容,看看是什么效果。将第13行的pass修改为下面的代码:

爬虫的运行

然后运行一下Scrapy的爬虫。需要注意的是,Scrapy的爬虫不能直接运行example.py这个文件,需要在CMD里面进入工程的根目录运行以下命令:

终端窗口中滚过很多的字符,但是似乎没有看到百度首页的源代码被打印出来。如果仔细看终端中的内容,将会发现下图中被圈出来的两条信息:

不过没关系,请打开工程中的settings.py文件,将会在里面看到:

再次运行,就可以看到百度的源代码被打印了出来:

由于Scrapy爬虫的运行方式和我们以前的爬虫不同,不能直接运行爬虫代码所在的.py文件。那如何使用Pycharm运行爬虫呢?

在工程的根目录下面创建一个main.py文件,内容如下:

然后使用Python来运行这个main.py文件即可。如下图所示:

还可以试一试在example.py文件中下一个断点并启动调试功能,会发现调试功能也可以正常运行。既然网页的源代码已经被拿下来了,那接下来的工作就是提取信息。

如果前面XPath学得不错的话,可以在5分钟之内掌握使用Scrapy提取信息的办法。请看下图第13和14行代码:

Scrapy与lxml在使用XPath的唯一不同之处在于,当要把获取到的字符串内容提取出来的时候,Scrapy的语句需要用.extract()这个方法。

如果暂时不需要把内容提取出来,那么可以先不需要使用.extract()方法,比如在先抓大再抓小这个技巧中,我们有两个XPath:

需要把每一对的链接和name文字对应起来。但是由于有时候,有一些name标签缺失,如果把它们分开获取就没有办法做到一一对应。所以可以这样写:

使用这种方式,就把链接和文字一一对应起来了。如果本身没有文字,就使用N/A代替。更多练习,可以访问http://exercise.kingname.info/overall

最后来讲讲以前遗留的问题mongodb安装为windows服务

之前第一章的时候介绍安装并启动了mongodb数据库,事实上每次通过命令行启动Mongo是痛苦的,因此我们需要建立一个永久性服务,这就需要我们把Mongo加入到Windows本地服务中去

先介绍一下目录结构

bin目录是mongodb程序目录 ,data\db\是数据存放目录 log是日志目录

在F:\mongodb\data下新建文件夹log(存放日志文件)并且新建文件mongodb.log在F:\mongodb新建文件mongo.config

用记事本打开mongo.config输入:

1

2

dbpath=D:\mongodb\data\db

logpath=D:\mongodb\data\log\mongo.log

然后再bin目录下打开命令行输入如下命令

mongod --dbpath "F:\mongodb\data\db" --logpath "F:\mongodb\data\log\mongodb.log" --install --serviceName "MongoDB"

显示为如下图所示说明服务已经安装成功了

mongodb相比mysql速度快了但就是安装起来就很不友好了!本次安环境为windows10 64专业版

其亲测可以使用!

十四:Scrapy与MongoDB

Scrapy的工程结构

当生成一个Scrapy的工程的时候,它生成了很多文件和文件夹。

其中需要关心的是:

spiders文件夹

items.py

pipelines.py

settings.py

spiders文件夹用于存放创建的爬虫文件。settings.py文件用于保存爬虫的各项设置信息。有这两项的情况下,就已经可以写出爬虫并保存数据了。

但是为什么还有items.py和pipelines.py这两个文件呢?这是由于Scrapy的理念是将数据爬取和数据处理分开。

items.py文件用于申明我们需要爬取哪些内容。每个内容都是一个Field.如下图所示:

在图中,需要爬取的内容是name, age 和salary。

pipelines.py文件用于保存初步处理数据的代码。可以将数据写入数据库的代码放在pipelines.py中。这个内容,将在下面做讲解。

Scrapy与MongoDB

环境准备

在Scrapy里面,数据的爬取和处理是分开来进行的。因此,在这一节里面,将要讲到如何正确地保存数据。由于我们做的是定向爬虫,所以我们知道自己需要哪些内容。因此,需要将这些内容写到items.py中,让Scrapy也知道要爬取的内容。

http://readcolor.com/lists 这个网站为例,需要爬取下面两幅图中框住的内容:

也就是:书单标题,书单中书的数量,每本书的链接,书单的作者,书单的创建时间,书单点进去以后的简介,每本书的名字,每本书的简介,每本书的作者。于是,创建一个爬虫,取名为readcolorSpider:

scrapy startproject readcolorSpider

然后在items.py中定义好需要的字段,如下图所示:

接下来,需要在settings.py 中配置MongoDB的一些信息,如下图所示:

在上图中,不仅增加了MongoDB的配置信息,还添加了pipelines的入口信息。在settings.py中,本来就已经有很多被注释的设置,在里面可以发现ITEM_PIPELINES相关的注释:

修改为

从而指定pipelines的入口。后面的数字300表示优先级。由于这里只有一个pipelines,所以后面的数字影响不大,默认即可。

再来看pipelines.py文件,在这里写了如何将信息插入到数据库中。如下图所示:

这个文件的逻辑非常的简单:

1.初始化MongoDB

2.插入数据

其中,如果你的MongoDB就在你运行爬虫的这台电脑上,你甚至可以把init方法这样写:

这样又少了几行代码。

虽然说items.py里面定义的是一个类,但是它可以使用Python的dict将它变成一个字典,然后直接插入到MongoDB中。现在万事俱备,那么就可以开始爬取数据了。

爬取数据

打开Scrapy自动生成的readcolor.py文件,将它的start_urls稍作修改:

这样Scrapy就会去这个地址下面爬数据了。

接下来,以书单的名字和书单的介绍为例来做讲解。抓取的代码不到30行,如下图所示:

我们对代码中的XPath已经非常熟悉了,所以对于从网页中读取内容的部分就不单独讲解。那么需要关注的应该是以下几点:

1.实例化ProgramItem

这一行是实例化ProgramItem这个类,这个类里面保存了之前定义好的需要抓取的内容。实例化好以后,可以像使用字典一样来使用它。

2.链接跟进

这一行代码涉及到了scrapy.Request这个方法,在这里例子中,给它传入了3个参数,分别是:

1.书单详情页的url

2.回调函数,用与继续在书单详情页中爬取信息

3.meta

由于在首页里面获取到的书单详情页的url是一个相对地址,所以需要给它加上域名,拼接成一个完整的url。

回调函数parse_book_list_detail用于处理详情页,并从中获取书单的简介。回调是异步中的一个概念。在这里可以理解为,当Scrapy获取到了详情页的源代码以后,再调用parse_book_list_detail这个方法,并将包含源代码的responese对象传递进去。

meta可以理解成一个传递信息的通道,它的值是一个字典,用于将parse方法中的信息传递给parse_book_list_detail。在代码中只传递了item,也可以传递其他信息进去。

这里还涉及到Python的一个关键字: yield。yield所在的位置和函数的return非常相似,但是它返回的东西是生成器对象,而不是某些具体的值。生成器里面的代码在被for循环迭代的时候才会执行且只执行一次。

关于yield,可以看这一个回答:http://stackoverflow.com/a/231855/3922976

3.储存数据

当我们对ProgramItem的实例item使用yield的时候,就会产生一个用于保存item中数据的生成器。这个生成器里面的代码就是将item中的数据通过pipeline中写的操作MongoDB的方法保存到数据库中。如下图所示为第一页的十个书单和简介:

十五:分布式爬虫的模型分析与创建

分布式架构

在这一课,将会使用一台DigitalOcean的Ubuntu服务器,一台系统为Arch Linux的树莓派,一台Windows 10 PC和一台Mac来搭建一个简化版的分布式的爬虫。这一次继续使用读远网站来做测试。这一课的重心是环境搭建和爬虫的运行,所以爬虫代码的开发只讲关键点。

我们将会使用DigitalOcean的Ubuntu服务器作为分布式爬虫的Master,用来做任务的派分。使用树莓派和Windows 10 PC还有Mac来作为分布式爬虫的Slave,用来爬取网站,并将结果保存到Mac 上面运行的MongoDB中。架构图如下:

其中,作为Master的Ubuntu服务器仅需要安装Redis即可,在这一课里,它仅仅是作为一个待爬网址的临时中转,所以甚至不需要安装Python。

在Mac,树莓派和Windows PC中,需要安装好Scrapy,并将代码拷进去运行。由于爬虫会一直监控Master的Redis,所以在Redis没有数据的时候爬虫处于待命状态。

当放一个起始URL到Redis以后,其中一个Slave爬虫就能开始运行了。它拿到这个URL以后,就会在Redis中将这个URL置为已经访问的状态,所以其他Slave不会重复抓取。而第一个拿到URL的爬虫将更多的URL放进Redis以后,其他爬虫就可以拿到这些URL并开始抓取,然后再放更多的URL进去。这就像是原子弹的链式反应,爬取速度越来越快,最后达到所有爬虫都全速运行的状态。

如何选择Master

在这一课的例子中,选择的服务器为DigitalOcean的Ubuntu来作为Master,它拥有一个公网IP,可以从世界上任何一个能访问互联网的地方访问。但并不是说一定要花钱买一个VPS。如果你自己有很多台电脑,你完全可以用一台电脑来作为Master,其他电脑来做Slave。

Master一定要能够被其他所有的Slave访问。所以,如果你的所有电脑不在同一个局域网,那么就需要想办法弄到一台具有公网IP的电脑或者VPS。在中国,大部分情况下被电信运营商分配到的IP地址是内网IP。在这种情况下,即使我知道了你的IP地址,我也没有办法连上你。大家可以试一试给运营商客服打电话,说你自己要安装网络监控,需要公网IP,态度要强硬一点。运气好的话,2-3天以后你就可以拥有公网IP,也就不需要买VPS服务器了。

但是在局域网里面也有一点不太好,因为局域网共用一个出口,局域网内的所有电脑,共用同一个公网IP。对网站来说,这个IP地址访问频率太高了,肯定不是人在访问,封!而我们使用分布式爬虫,不仅仅是为了提高访问抓取速度,更重要的是降低每一个IP的访问频率,使网站误认为我们这是人在访问。

所以,大家在实际生产环境中,最理想的情况是每一个Slave的公网IP都不一样,这样才能做到既能快速抓取,又减小被反爬虫机制封锁的机会。

购买VPS与搭建环境

在大多数情况下,需要使用很多的VPS来运行爬虫。在购买VPS以后,应该如何登录呢?大家可以使用PuTTY来登录VPS。它的官方首页为:http://www.putty.org/ 当下载安装并运行这个程序以后,你可以看到如下界面:

在“Host Name (or IP address)” 这个输入框中输入你的VPS的IP地址,然后点击Open,在打开的SSH窗口中输入用户名root和密码就能控制VPS了,如下图所示:

注意:在输入密码的时候,PuTTY的窗口是不会有星号出现的。这是安全措施,并非服务器没有响应。你闭着眼睛输入完密码并回车即可。

如何设置数据库

1.Redis

如果直接使用redis-server来运行Redis,那么这个时候只能在本地访问,无法让其他电脑访问Redis。你需要使用下面的命令来启动Redis才可以从外部访问Redis:

2.MongoDB

在默认情况下,Linux和Mac OS的MongoDB是不能被外部访问的,需要做一些设置。如果你的MongoDB是在Mac OS中,并且使用homebrew安装,请修改:

将bindIp修改为你的外网IP或者内网IP。如下图所示:

注意:这里的IP地址是运行MongoDB的这台电脑自己的IP地址,如果你希望同一个局域网的其他电脑访问MongoDB,那么就将这里设置成运行MongoDB这台电脑的内网IP地址,如果你希望整个互联网的其他电脑访问这个MongoDB,就将这里设置成运行这个MongoDB的这台电脑的外网IP地址。

如果你的MongoDB是运行在Linux上,或者在Mac OS上面但是你没有使用homebrew安装,那么mongod.conf的位置位置可能在:

修改完成以后请重启MongoDB。

十六:复杂的分布式爬虫

在上一章,已经实现了一个简单的分布式爬虫。这个爬虫由一个Master和3个Slave组成。我们爬取的网站几乎没有任何的反扒措施。这样的网站现在已经越来越难找了。

那么对于有反爬虫机制的网站,应该如何设计分布式爬虫呢?下面根据各个情况来一一分析,有一些地方,和单机别无二致,而另一些地方,就有显著的不同。

中间件 Middleware

想象这样一个场景,C国通过一名特工给J国传递情报,在传递的过程中,特工B要经过一个中转站,可是出来并不是特工B,而是特工M,情报的内容也遭到了篡改。

上面这个例子中的中转站,就是我们的Middleware,中文名叫做中间件。本来我们的爬虫是直接访问网站的,但是设置了中间件以后,爬虫会先跑到中间件里面做一些不可告人的勾当,然后再去访问网站。

这里的不可告人的勾当,包括但不限于:更换代理IP,更换Cookie,更换User-Aget。

中间件的开发

Scrapy有两种中间件,Downloader Middleware和Spider Middleware。我们来讲讲用的最多的Downloader Middleware。

在爬虫开发中,更换代理IP是非常常见的情况,有时候甚至每一次访问都需要随机选择一个代理IP来进行。我们以更换代理IP为例来讲解如何开发一个中间件。

中间件本身是一个Python的类,更换IP是在访问每个网页之前。所以,会用到process_request方法,这个方法中的代码会在每次爬虫访问网页之前执行。请看下面的代码:

这两个类UAMiddleware和ProxyMiddleware实现了在每次访问网页的时候更换User-Agent和代理IP。我将备选的User-Agent和代理IP写在了settings.py文件中,如下图所示:

当然,你也可以将它们写到数据库或者Redis中。所以,我们的爬虫系统可以这样写:

1.写一个小爬虫ProxySpider去各大代理网站爬取免费代理并验证。将可以使用的代理IP保存到数据库中。

2.在ProxyMiddlerware的process_request中,每次从数据库里面随机选择一条代理IP地址使用。

3.周期性验证数据库中的无效代理,及时将其删除。

middlewares.py的文件名,以及ProxyMiddleware这个类名你改成其他的也可以,不过一般约定俗成这样写,类名统一使用“功能Middleware”例如:UserAgentMiddleware, CookieMiddleware, ProxyMiddleware…

虽然中间件已经写好了,我们现在需要让Scrapy使用它。大家在Scrapy的settings.py中找找有没有这样一段被注释掉的话:

我们把这几段解除注释,再将其中的:

修改为:

这样,Scrapy在每次访问网页的时候,就会更换代理IP了。

后面的数字表示优先级。数字越小,优先级越高,越先执行。例如如果你有一个更换代理的中间件,还有一个更换Cookie的中间件,那么更换代理的中间件显然是需要在更换Cookie的中间件之前运行的。如果你把这个数字设为None,那么就表示禁用这个中间件。

其他中间件

在Downloader Middleware中,还有

其中process_response会在爬虫爬到网页的源代码以后执行。process_exception会在发生异常的时候执行。这两个方法大家可以参考Scrapy的官方文档进行学习:

http://doc.scrapy.org/en/latest/topics/downloader-middleware.html?highlight=middleware

关于Spider Middleware可以参考:

http://doc.scrapy.org/en/latest/topics/spider-middleware.html?highlight=middleware

十七:维护多个Session

分布式爬虫的登录

Session是一段保存在服务器上面的信息,用来验证用户的身份。在客服端的Cookie中,保存了Session ID,服务器通过这个Session ID来找到对应的这个用户的Session。

不论是模拟登录还是在爬取数据的过程中输入突然出现的验证码,归根到底是修改服务器上面对应你的那一段Session。

只有你的Session被服务器允许直接访问页面,你的爬虫才可以访问页面。否则就会让你登录,或者弹出一个验证码输入框。

分布式爬虫的登录

如果网站需要登录才能爬取数据,那么登录的过程应该在爬虫开始爬取需要的内容之前先完成,并获取(足够多的)Cookie来供我们的爬虫使用。这里我把“足够多的”打上了括号,是因为有些时候只需要一个或者不需要Cookie,但有时候如果只使用一个账号的Cookie,很容易被屏蔽,所以需要注册很多的账号,并用这些账号的Cookie来运行爬虫。

登录网站获取Cookie这件事,还是使用中间件来完成。

假设获取Cookie的中间件叫做CookieMiddleware, 那么它可以这样写:

假设我有100个账号和密码,那么我就得到100个账号的Cookie,然后再随机选择一个赋值给request.cookies即可。

上面的代码演示了设定Cookie的过程。但是如果有100个账号,我们不可能在访问每个网页的时候,都把每个账号重新登录一次,这样会大大降低访问的速度。正确的做法是,把使用requests往服务器post账号并返回Cookie的这部份代码单独存放到另外一个文件中,一次性获取所有的Cookie,再将Cookie放到一个列表中,供CookieMiddleware的process_request随机选用。如下面两张图所示:

当然,在实际的登录中,很少网站只需要用户名和密码就可以登录的,大多数情况下还需要一些其他的东西,像是验证码啊,是否保持登录啊,或者网页中的一些ID啊。所以需要根据实际情况来修改login.py

一些不会经常改变的常量信息可以使用settings.py来存放,如下图所示:

当然,账号也可以像代理IP一样,存放在一个数据库中,爬虫定期使用新的账号的Cookie来替换旧的账号的Cookie。

Scrapy与Selenium

在定向爬虫进阶课程中,使用Selenium和ChromeDriver来充当浏览器运行网站中的JavaScript或者帮助我们填充表单。在Scrapy中,也可以使用Selenium和ChromeDriver。

实际上,在Scrapy中使用Selenium和在之前的课程中使用Selenium没有什么本质的区别,甚至可以直接将以前的代码放在爬虫的parse方法中。但是这样做有一个坏处,那就是每爬取一个网页就要开一个浏览器窗口,这是非常浪费时间的行为。

因此,一般只初始化一次浏览器,之后的所有网页访问都在这一个浏览器窗口中进行。但是Scrapy是多线程的,parse方法会被很多个线程调用,所以初始化浏览器的代码不能放在parse里面。那么这一段代码应该放在哪里呢?

Scrapy的Spider本质上是一个类,平时写类的时候,一般都有一个init方法,那么Scrapy的Spider是否也可以呢?答案当然是肯定的。请大家参看下面的代码:

在这段代码中,我就为爬虫实现了__init__方法,并将Selenium初始化浏览器的代码放在了这里。

当平时不写__init__方法的时候,参数传递会由Python自动实现。但是如果我们显式的写出了,那么就需要使用super方法来将参数传递给父类scrapy.Spider:

当然这里的父类还可以使用RedisSpider或者CrawlSpider等等。

下图为上面的代码运行以后所爬取到的结果:

在上图的结果中,白色的部分就是电视剧的评论。由于直接使用print函数将评论打印在控制台中,所以会看到评论内容与Scrapy的日志混在了一起。大家自己在实际操作的时候,可以按照正常的流程,把评论放到Item中,再通过pipelines保存到数据库中。

使用Selenium的另一个缺陷,即便有Scrapy也无法避免,那就是极容易受网速影响,网速不好的时候速度极其缓慢。它会等待网页完全加载完成才开始获取内容。有时候一个页面被一个JS脚本卡住,完全加载完成要好几分钟,白白浪费Scrapy的优秀性能。

但是在测试过程中,也有过几次网速出乎意料的好,爬完两个网页“仅仅”用了15秒钟。大家想一想,如果不用Selenium,凭借Scrapy的性能,15秒钟都可以爬几百个页面了。可是用了Selenium,15秒钟爬两个页面我竟然还觉得速度挺快。

这个感觉就像是你在从C 盘复制文件到D盘,速度10M/s,你大骂无良商家垃圾硬盘速度这么慢。但是如果你从网上下载东西的时候速度有10M/s,你惊呼这速度简直快如闪电。(老师家里的小水管,下载速度满速2.5M/s)

所以各位在决定是否要使用Scrapy 和Selenium配合之前,一定要慎重考虑,如果不是网站的反爬虫机制过于变态,不建议大量使用Selenium。

验证码

这里说的验证码不是在登录的时候让你输入的验证码,而是在网站爬取的过程中突然弹出来的验证码。这种情况一般是网站发现了你的访问有异常,所以自动跳转到一个验证码页面,只有正确输入验证码以后才能继续访问网站。

这种验证码,如果使用的是单线程爬虫,那非常好处理:发现验证码→下载验证码→识别验证码→回填验证码→继续爬数据。但如果你的爬虫是多线程爬虫怎么办?难道每个线程遇到了验证码都去下载→识别→回填吗?当然不用,我们可以让爬虫停下来,更新Session然后再重新启动。

这里又说到了Session。网站判断我们是否可以访问,实际上是判断你的Session是否有权限访问。而判断Session,实际上就是拿你的Cookie里面的Session ID,然后去服务器上面查询这个Session ID对应的Session是否有权限访问。当网站给你弹验证码的时候,说明它已经认为这个Session的用户很可疑了。所以要检查一下是人还是程序。所以这个时候,如果我们的爬虫能及时更新Session ID,那么自然而然就可以继续爬取数据了。

在Scrapy中,有一个方法可以让爬虫暂停下来:

相应的,也有一个方法可以解除暂停:

在爬虫中调用这两个方法,可以让爬虫自己暂停自己,或者自己解除自己的暂停。

所以,在爬虫的某一个线程遇到验证码的时候,就调用:

这个时候,执行这一行代码的这个线程自己不会被暂停,但是爬虫的其他线程会被暂停。那么这个没有被暂停的线程,现在就肩负起了识别验证码的重任。它需要识别验证码,回填验证码,从而拿到新的Cookie,再将新的Cookie更新。由于Session ID是存放在Cookie中的,因此Session ID也随之更新。更新完成以后,这个线程继续调用:

重新让其他的线程开始爬取。由于Session是所有线程共用的,所以其他线程也就不会遇到验证码了。下图为一个简化版的例子:

关于如何更新Session,大家可以参考第二节《维护多个Session》中所讲的内容。填写验证码就是一个模拟填写表单并提交的过程。提交以后返回的Cookie更新到request.cookie即可。

到这里python爬虫系列系统课程就完成了!后面过几天将会更新一个新浪爬虫这几天整理一下发几天其它东西。

扩展阅读:Python爬虫学习系列教程

转自:http://www.bigdataway.net

猜你喜欢

转载自blog.csdn.net/qq_39513887/article/details/87902360