你好,我是悦创。 本篇将开启我自己啃代理池的心得,将逐步放送,因为代理池搭建较为复杂,这里我就尽可能把代理池分成几篇来讲,同时也保证,在我其他篇放出来之前,每一篇都是你们的新知识。 学习就像看小说一样,一次一篇就会显得额外的轻松! 当你把学习当作某个娱乐的事情来做,你会发现不一样的世界! 我们无法延长生命长度,但是我们延长生命宽度,学习编程就是扩展生命最有力的武器!
1. 看完之后你会得到什么
- 返回 yield;
- eval 的使用;
- 多个代理网站同时抓取;
- 使用异步测试代理是否可用;
- Python 的元类编程简单介绍;
- 正则表达式、PyQuery 提取数据;
- 模块化编程;
废话不多说,马上步入正题!
2. 你需要的准备
在学习本篇文章时,希望你已经具备如下技能或者知识点:
- Python 环境(推荐 Python 3.7+);
- Python 爬虫常用库;
- Python 基本语法;
- 面向对象编程;
- yield、eval 的使用;
- 模块化编程;
3. 课前预习知识点
对于代理池的搭建呢,虽然我已经尽可能地照顾到绝大多数地小白,把代理地的搭建呢,进行了拆分,不过对于培训机构或者自学不是特别精进的小伙伴来说还是有些难度的,对于这些呢?我这里也给大家准备了知识点的扫盲。以下内容节选自我个人博客文章,这里为了扫盲就提供必要部分,想看完整的文章可以点击此链接:https://www.aiyc.top/archives/605.html
3.1 Python 的元类编程
你好,我是悦创。 好久不见,最近在啃数学、Java、英语没有来更新公众号,那么今天就来更新一下啦! 又到了每日一啃,啃代码的小悦。今天我遇到了 Python 原类,然而我啥也不懂。只能靠百度和谷歌了,主要还是用谷歌来查啦,百度前几条永远是广告准确度也不行(个人观点),也顺便参考了几个博客:廖雪峰网站,添加了一点自己的观点和理解。
3.1.1 type()
动态语言和静态语言最大的不同,就是函数和类的定义,不是编译时定义的,而是运行时动态创建的。 比方说我们要定义一个 Hello
的 class,就写一个 hello.py
模块(这里我实际创建的是:the_test_code_project.py
模块):
1 |
class Hello(object): |
当 Python 解释器载入 hello
模块时,就会依次执行该模块的所有语句,执行结果就是动态创建出一个 Hello
的 class 对象,测试如下:
1 |
if __name__ == '__main__': |
运行结果如下:
1 |
Hello, world. |
其中,上面的输出结果:__main__.Hello
等价于 <class 'the_test_code_project.Hello'>
运行的方式不同显示的方式也不同,但含义是一样的。 type()
函数可以查看一个类型或变量的类型,为了让小白更轻松,我也写了个例子:
1 |
number = 12 |
运行结果:
1 |
<class 'int'> |
Hello
是一个 class,它的类型就是 type
,而 h
是一个实例,它的类型就是class Hello
。 我们说 class 的定义是运行时动态创建的,而创建 class 的方法就是使用 type()
函数。 type()
函数既可以返回一个对象的类型,又可以创建出新的类型,比如,我们可以通过 type()
函数创建出Hello
类,而无需通过 class Hello(object)...
的定义:
1 |
def fn(self, name='world'): # 先定义函数 |
我们接下来来调用一下代码,看输出的结果如何:
1 |
if __name__ == '__main__': |
这里推荐写成:if __name__ == '__main__':
使代码更加的规范。 运行结果:
1 |
Hello, world. |
要创建一个 class 对象,type()
函数依次传入 3 个参数:
- class 的名称;
- 继承的父类集合,注意 Python 支持多重继承,如果只有一个父类,别忘了 tuple 的单元素写法;(这个个 tuple 单元素写法起初本人不太理解,然后一查并认真观察了一下上面的代码就想起来 tuple 单元素写法需要加逗号(,),就是你必须这么写:
tuple_1 = (1,)
而不能这么写:tuple_2 = (1)
,tuple_2 = (1)
的写法,Python 会自动认为是一个整数而不是一个元组) - class 的方法名称与函数绑定,这里我们把函数
fn
绑定到方法名hello
上。
通过 type()
函数创建的类和直接写 class 是完全一样的,因为 Python 解释器遇到 class 定义时,仅仅是扫描一下 class 定义的语法,然后调用 type()
函数创建出 class。(直接 Class 创建也是) 正常情况下,我们都用 class Xxx...
来定义类,但是,type()
函数也允许我们动态创建出类来,也就是说,动态语言本身支持运行期动态创建类,这和静态语言有非常大的不同,要在静态语言运行期创建类,必须构造源代码字符串再调用编译器,或者借助一些工具生成字节码实现,本质上都是动态编译,会非常复杂。
3.1.2 metaclass
除了使用 type()
动态创建类以外,要控制类的 创建行为 ,还可以使用 metaclass。 metaclass,直译为元类,简单的解释就是:
- 当我们定义了类以后,就可以根据这个类创建出实例,所以:先定义类,然后创建实例。
- 但是如果我们想创建出类呢?
那就必须根据 metaclass 创建出类,所以:先定义 metaclass ,然后创建类。连接起来就是:先定义 metaclass ,就可以创建类,最后创建实例。 所以,metaclass 允许你创建类或者修改类 。换句话说,你可以把类看成是 metaclass 创建出来的“实例”。
metaclass 是 Python 面向对象里最难理解,也是最难使用的魔术代码。正常情况下,你不会碰到需要使用metaclass的情况,所以,以下内容看不懂也没关系,因为基本上你不会用到。(然而还是被我遇见了,而且还是看不懂,但经过大佬的指点就只是知道如何使用,但并不了解其中的原理,所以才有了此篇。)
我们先看一个简单的例子,这个 metaclass 可以给我们自定义的 MyList 增加一个 add
方法:
1 |
class ListMetaclass(type): |
定义
ListMetaclass
,按照默认习惯,metaclass 的类名总是以 Metaclass 结尾,以便清楚地表示这是一个metaclass 。
有了 ListMetaclass ,我们在定义类的时候还要指示使用 ListMetaclass 来定制类,传入关键字参数 metaclass
:
1 |
class MyList(list, metaclass=ListMetaclass): |
当我们传入关键字参数 metaclass
时,魔术就生效了,它指示 Python 解释器在创建 MyList
时,要通过ListMetaclass.__new__()
来创建,在此,我们可以修改类的定义,比如,加上新的方法,然后,返回修改后的定义。 __new__()
方法接收到的参数依次是:
- 当前准备创建的类的对象;
- 类的名字;
- 类继承的父类集合;
- 类的方法集合。
测试一下 MyList
是否可以调用 add()
方法:
1 |
L = MyList() |
而普通的 list
没有 add()
方法:
1 |
\>>> L2 = list() |
这时候,我想你应该和我会有同样的问题,动态修改有什么意义? 直接在 MyList
定义中写上 add()
方法不是更简单吗? 正常情况下,确实应该直接写,我觉得通过 metaclass 修改纯属变态。
3.2 Python eval() 函数
对于 Python 的 eval 呢,大部分人是这么定义的:eval() 函数用来执行一个字符串表达式,并返回表达式的值。 这句话,有可能对于新手来说并不是非常好理解,我们还是用例子来顿悟一下吧。 以下是 eval() 方法的语法:
1 |
eval(expression[, globals[, locals]]) |
参数
- expression — 表达式。
- globals — 变量作用域,全局命名空间,如果被提供,则必须是一个字典对象。
- locals — 变量作用域,局部命名空间,如果被提供,可以是任何映射对象。
返回值 返回表达式计算结果。 实际操作
1 |
In [3]: x = 7 |
再来个函数的操作:
1 |
In [1]: str1 = "print('Hello World')" |
ok,到此零基础小白关怀文章就完成了,我就不继续赘述啦! 看到这里,你们的身体还行吗? 我正在的干货要开始了! 进入我们的 show time 环节,起飞了、起飞了,做好准备了!
4. 抓取免费代理
这里呢,我就不再带带大家手摸手的教学如何抓取代理了,因为当你能看这篇文章时,相信你已经对爬虫是有所入门了,如果还没入门的小伙伴可以关注本公众号,往期文章也有零基础教学和基础的课程资源,可以公众号后台回复:Python爬虫。即可获取资源,如果失效也别担心加小悦好友即可!(资源多多噢!)
小白不建议报名培训机构,毕竟现在培训结构收智商税比较多,还是需要多多鉴别噢!
4.1 目标的代理网站
- 快代理:https://www.kuaidaili.com/
- 西刺代理:https://www.xicidaili.com
- 66代理:http://www.66ip.cn/
- 无忧代理:http://www.data5u.com
- 开心代理-高匿:http://www.kxdaili.com/dailiip/
- 云代理:http://www.ip3366.net/free/
对于每个网站的代码呢,具体实现也是比较简单的,这里我就不做过多的赘述啦! 接下来我都直接上代码,不过在上代理的爬虫代码前,我们需要来写个元类,代码如下:
1 |
class ProxyMetaclass(type): |
详细的代码含义,已经写在上面了,具体的这里 就不赘述了,如果泥有任何不理解的可以去点击阅读原文在我的博客网站下留言,和后台回复数字“3”,加小编好友,拉你入交流群。
4.2 代码编写
4.2.1 请求函数编写
单独保存成一个文件:utils.py
1 |
""" |
上面请求函数编写完成之后,我们就需要在抓取代理的代码文件中,进行导包,代码如下:
1 |
# files:Spider.py |
4.2.2 代理抓取代码
导入所需要的库:
1 |
import requests |
接下来我们需要创建一个类 FreeProxyGetter 并使用元类创建。
1 |
class FreeProxyGetter(object, metaclass=ProxyMetaclass): |
1. 快代理
1 |
def crawl_kuaidaili(self): |
2. 西刺代理
1 |
def crawl_xicidaili(self): |
3. 66代理
1 |
def crawl_daili66(self, page_count=4): |
4. 无忧代理
1 |
def crawl_data5u(self): |
5. 开心代理-高匿
1 |
def crawl_kxdaili(self): |
6. 云代理
1 |
def IP3366Crawler(self): |
至此,代理网站的代码已经全部编写完成,接下来我们需要了解的是然后调用此类,直接调用吗? 显然不是,我们还需要编写一个运行调用的函数,代码如下:
1 |
def run(self): |
以上代理的代码中,我并没有抓取每个网站全部页数,如果有需要可以自行调节。 这里我们可以直接写一个调用代码,来测试代码是否正常,写在:Spider.py 也就是代理的代码中,代码如下:
1 |
if __name__ == '__main__': |
运行结果如下: 结果较多,省略大部分结果。
1 |
Getting 14.115.70.177:4216 from crawl_xicidaili |
ok,至此代理成功抓取,接下来就是我们的异步检测了。
4.2.3 异步检测
这里,我们需要编写一个 error.py 来自定义包:
1 |
class ResourceDepletionError(Exception): |
异步检测代码,这里我就不做任何数据存储,有需求的可以自行添加,下一篇将添加一个存入 redis 数据库的,敬请关注!
1 |
# files:schedule.py |
对于上面的部分代理代码,我为了输出结果更加直观,把 print() 输出的,包括 utils.py 的 print() 全部注释了, 运行结果(省略部分输出)
1 |
Testing 182.46.252.84:9999 |
到此,多站抓取异步检测,就已经完全实现,也是实现了模块化编程。我也 顺便把项目结构分享一下:
1 |
C:. |
本篇我想你主要学习到的目标也就是开头所说的,这里就不赘述了,下一篇我将带大家把抓取到可用的代理进行存储到 redis 数据库中,尽请关注! 源代码文件获取:公众号后台回放:ProxyPool-1 也欢迎加入我的交流群,一起交流!