对于网络上的公开数据,理论上只要由服务端发送到前端都可以由爬虫获取到。但是Data-age时代的到来,数据是新的黄金,毫不夸张的说,数据是未来的一切。基于统计学数学模型的各种人工智能的出现,离不开数据驱动。数据采集、清洗是最末端的技术成本,网络爬虫也是基础采集脚本。但是有几个值得关注的是:

  • 对于实时变化的网络环境,爬虫的持续有效性如何保证
  • 数据采集、清洗规则的适用范围
  • 数据采集的时间与质量–效率
  • 爬与反爬的恩怨
  • 爬虫的法律界限

法律的边界,技术无罪

对于上面几个关注点,我最先关注的便是爬虫的法律界限 ,我曾经咨询过一个律师:

Q: 老师,我如果用爬虫爬取今日头条这种类型网站的千万级公开数据,算不算违法呢?
A: 爬取的公开数据不得进行非法使用或者商业利用

简单的概括便是爬虫爬取的数据如果进行商业出售或者有获利的使用,便构成了“非法使用”。而一般的爬虫程序并不违法,其实这是从法律专业的一方来解读,如果加上技术层面的维度,那么应该从这几方面考虑:

  • 爬取的数据量
  • 爬取数据的类型(数据具有巨大的商业价值,未经对方许可,任何人不得非法获取其数据并用于***经营行为***)
  • 爬取的数据用途 (同行竞争?出售?经营?分析?实验?…)
  • 是否遵循网站的robots.txt 即 机器人协议
  • 爬取行为是否会对对方网站造成不能承受的损失(大量的爬取请求会把一个小型网站拖垮)

其实爬虫构成犯罪的案例是开始增多的,相关新闻:

  1. 当爬虫遇上法律会有什么风险?
  2. 程序员爬虫竟构成犯罪?
  3. 爬虫相关法律知识

如果你的上级或公司要求你爬取某些网站的大量公开数据,你会怎么办呢?可以参考第2条新闻。法律矛盾点关键在于前面考虑的前三点,如果是个人隐私数据,是不能爬取的,如果是非公开数据,是不能爬取的,而对于其他大量的公开数据爬取,看人家查不查的到你,要不要起诉你。技术在你的手上,非法与否在于你怎么去用。最好的爬取道德原则是:

  • 减少并发请求
  • 延长请求间隔
  • 不进行公开出售数据
  • 遵循网站 robots协议

当然,反爬最有效的便(目的均在于拦截爬虫进入网站数据范围)是:

  • 要求用户密码+验证码
  • 加密数据
  • js混淆
  • css混淆
  • 针对IP请求频率封锁
  • 针对cookie、session单个账户请求频率封锁单日请求次数
  • 对关键数据进行拆分合并
  • 对爬虫投毒(返回假数据)
  • 完善robots.txt
  • 识别点击九宫图中没有包含xxx的图片等(终极验证码)
  • 设置黑白名单、IP用户组等

工欲善其事

针对网站的公开数据进行爬取,我们一般都要先对网站数据进行分析,定位,以确定其采集规则,如果网站设置了访问权限,那么便不属于我们的爬虫采集范围了:)
分析好采集规则,写好了采集数据持久化(存入数据库、导出为word、excel、csv、下载等)的相关代码,整个爬虫运行正常。那么怎样才能提高采集速度呢?

  • 多进程采集
  • 多线程采集
  • 异步协程采集
  • 多进程 + 多线程采集
  • 多进程 + 异步协程采集
  • 分布式采集

异步爬虫是同步爬虫的升级版,在同步爬虫中,无论你怎么优化代码,同步IO的阻塞是最大的致命伤。同步阻塞会让采集任务一个个排着长队领票等待执行。而异步采集不会造成IO阻塞,充分利用了IO阻塞任务的等待时间去执行其他任务。

大家在学python的时候肯定会遇到很多难题,以及对于新技术的追求,这里推荐一下我们的Python学习扣qun:784758214,这里是python学习者聚集地!!同时,自己是一名高级python开发工程师,从基础的python脚本到web开发、爬虫、django、数据挖掘等,零基础到项目实战的资料都有整理。送给每一位python的小伙伴!每日分享一些学习的方法和需要注意的小细节

点击:python技术分享交流

在IO 模型中,只有IO多路复用(I/O multiplexing){在内核处理IO请求结果为可读或可写时调用回调函数} 不阻塞 “内核拷贝IO请求数据到用户空间”这个过程,实现异步IO操作。

同步爬虫

一般的同步爬虫,我们可以写一个,(以爬取图片网站图片为例),我们来看看其下载该网址所有图片所花费的时间:

以下代码为后面多个例程的共同代码:

#coding:utf-8
import time
from lxml import etree
import urllib.request as request#目标网址
url = 'http://www.quanjing.com/creative/SearchCreative.aspx?id=7'def download_one_pic(url:str,name:str,suffix:str='jpg'):#下载单张图片path = '.'.join([name,suffix])response = request.urlopen(url)wb_data = response.read()with open(path,'wb') as f:f.write(wb_data)def download_many_pic(urls:list):#下载多张图片start = time.time()for i in urls:ts = str(int(time.time() * 1000))download_one_pic(i, ts)end = time.time()print(u'下载完成,%d张图片,耗时:%.2fs' % (len(urls), (end - start)))def get_pic_urls(url:str)->list:#获取页面所有图片链接response = request.urlopen(url)wb_data = response.read()html = etree.HTML(wb_data)pic_urls = html.xpath('//a[@class="item lazy"]/img/@src')return pic_urlsdef allot(pic_urls:list,n:int)->list:#根据给定的组数,分配url给每一组_len = len(pic_urls)base = int(_len / n)remainder = _len % ngroups = [pic_urls[i * base:(i + 1) * base] for i in range(n)]remaind_group = pic_urls[n * base:]for i in range(remainder):groups[i].append(remaind_group[i])return [i for i in groups if i]

同步爬虫:

def crawler():#同步下载pic_urls = get_pic_urls(url)download_many_pic(pic_urls)

执行同步爬虫,

crawler()

输出(时间可能不一样,取决于你的网速):

下载完成,196张图片,耗时:49.04s

在同一个网络环境下,排除网速时好时坏,可以下载多几次取平均下载时间,在我的网络环境下,我下载了5次,平均耗时约55.26s

多进程爬虫

所以为了提高采集速度,我们可以写一个多进程爬虫(以爬取图片网站图片为例):
为了对应多进程的进程数n,我们可以将图片链接列表分成n组,多进程爬虫:

from multiprocessing.pool import Pool
def multiprocess_crawler(processors:int):#多进程爬虫pool = Pool(processors)pic_urls = get_pic_src(url)#对应多进程的进程数processors,我们可以将图片链接列表分成processors组url_groups = allot(pic_urls,processors)for i in url_groups:pool.apply_async(func=download_many_pic,args=(i,))pool.close()pool.join()

执行爬虫,进程数设为4,一般是cpu数量:

multiprocess_crawler(4)

输出:

下载完成,49张图片,耗时:18.22s
下载完成,49张图片,耗时:18.99s
下载完成,49张图片,耗时:18.97s
下载完成,49张图片,耗时:19.51s

可以看出,多进程比原先的同步爬虫快许多,整个程序耗时19.51s,为什么不是同步爬虫的55s/4 ≈ 14s呢?因为进程间的切换需要耗时。
如果把进程数增大,那么:

进程数:10 , 耗时:12.3s
进程数:30 , 耗时:2.81s
进程数:40 , 耗时:11.34s

对于多进程爬虫来说,虽然实现异步爬取,但也不是越多进程越好,进程间切换的开销不仅会让你崩溃,有时还会让你的程序崩溃。一般用进程池Pool维护,Pool的processors设为CPU数量。进程的数量设置超过100个便让我的程序崩溃退出。使用进程池可以保证当前在跑的进程数量控制为设置的数量,只有池子没满才能加新的进程进去。

多线程爬虫

多线程版本可以在单进程下进行异步采集,但线程间的切换开销也会随着线程数的增大而增大。当线程间需要共享变量内存时,此时会有许多不可预知的变量读写操作发生,python为了使线程同步,给每个线程共享变量加了全局解释器锁GIL。而我们的爬虫不需要共享变量,因此是线程安全的,不用加锁。多线程版本:

import random
from threading import Threaddef run_multithread_crawler(pic_urls:list,threads:int):begin = 0start = time.time()while 1:_threads = []urls = pic_urls[begin:begin+threads]if not urls:breakfor i in urls:ts = str(int(time.time()*10000))+str(random.randint(1,100000))t = Thread(target=download_one_pic,args=(i,ts))_threads.append(t)for t in _threads:t.setDaemon(True)t.start()for t in _threads:t.join()begin += threadsend = time.time()print(u'下载完成,%d张图片,耗时:%.2fs' % (len(pic_urls), (end - start)))def multithread_crawler(threads:int):pic_urls = get_pic_src(url)run_multithread_crawler(pic_urls,threads)

并发线程数太多会让我们的系统开销越大,使程序花费时间越长,同时也会增大目标网站识别爬虫机器行为的几率。因此设置好一个适当的线程数以及爬取间隔是良好的爬虫习惯。
执行多线程爬虫,设置线程数为50

multithreads_crawler(50)

输出:

下载完成,196张图片,耗时:3.10s

增大线程数,输出:

线程数:50,耗时:3.10s
线程数:60,耗时:3.07s
线程数:70,耗时:2.50s
线程数:80,耗时:2.31s
线程数:120,耗时:3.67s

可以看到,线程可以有效的提高爬取效率,缩短爬取时间,但必须是一个合理的线程数,越多有时并不是越好的,一般是几十到几百个之间,数值比多进程进程数大许多。

异步协程爬虫

Python3.5引入了async/await 异步协程语法。详见PEP492
由于asyncio提供了基于socket的异步I/O,支持TCP和UDP协议,但是不支持应用层协议HTTP,所以需要安装异步http请求的aiohttp模块
单进程下的异步协程爬虫:

import asyncio
from asyncio import Semaphore
from aiohttp import ClientSession,TCPConnectorasync def download(session:ClientSession,url:str,name:str,sem:Semaphore,suffix:str='jpg'):path = '.'.join([name,suffix])async with sem:async with session.get(url) as response:wb_data = await response.read()with open(path,'wb') as f:f.write(wb_data)async def run_coroutine_crawler(pic_urls:list,concurrency:int):# 异步协程爬虫,最大并发请求数concurrencytasks = []sem = Semaphore(concurrency)conn =TCPConnector(limit=concurrency)async with ClientSession(connector=conn) as session:for i in pic_urls:ts = str(int(time.time() * 10000)) + str(random.randint(1, 100000))tasks.append(asyncio.create_task(download(session,i,ts,sem)))start = time.time()await asyncio.gather(*tasks)end = time.time()print(u'下载完成,%d张图片,耗时:%.2fs' % (len(pic_urls), (end - start)))def coroutine_crawler(concurrency:int):pic_urls = get_pic_src(url)loop = asyncio.get_event_loop()loop.run_until_complete(run_coroutine_crawler(pic_urls,concurrency))loop.close()

执行异步协程爬虫,设置最大并发请求数为100:

coroutine_crawler(100)

输出:

下载完成,196张图片,耗时:2.27s

可以看出,异步多协程的下载请求效率并不比多线程差,由于磁盘IO读写阻塞,所以还可以进一步优化,使用aiofiles。
针对比较大的多媒体数据下载,异步磁盘IO可以使用aiofiles,以上述例子download可以改为:

import aiofiles
async def download(session:ClientSession,url:str,name:str,sem:Semaphore,suffix:str='jpg'):path = '.'.join([name,suffix])async with sem:async with session.get(url) as response:async with aiofiles.open(path,'wb') as fd:while 1:wb_data_chunk = await response.content.read(1024)if not wb_data_chunk:breakawait fd.write(wb_data_chunk)

多进程 + 多线程 爬虫

实际采集大量数据的过程中,往往是多种手段来实现爬虫,这样可以充分利用机器CPU,节省采集时间。
下面使用多进程(进程数为CPU数,4)+ 多线程 (线程数设为50)来对例子进行更改(上面各个例子导入的模块默认使用):

def mixed_process_thread_crawler(processors:int,threads:int):pool = Pool(processors)pic_urls = get_pic_src(url)url_groups = allot(pic_urls,processors)for group in url_groups:pool.apply_async(run_multithread_crawler,args=(group,threads))pool.close()pool.join()

执行爬虫:

mixed_process_thread_crawler(4,50)

输出:

下载完成,49张图片,耗时:2.73s
下载完成,49张图片,耗时:2.76s
下载完成,49张图片,耗时:2.76s
下载完成,49张图片,耗时:2.76s

采集时间与异步协程和多线程并无多大的差异,可以使用更大数据量做实验区分。因为多进程+多线程,CPU切换上下文也会造成一定的开销,所以进程数与线程数不能太大,并发请求的时间间隔也要考虑进去。

多进程 + 异步协程 爬虫

使用多进程(进程数为CPU数,4)+ 异步协程(最大并发请求数设为50)来对例子进行更改(上面各个例子导入的模块默认使用):

def _coroutine_crawler(pic_urls:list,concurrency:int):loop = asyncio.get_event_loop()loop.run_until_complete(run_coroutine_crawler(pic_urls, concurrency))loop.close()def mixed_process_coroutine_crawler(processors:int,concurrency:int):pool = Pool(processors)pic_urls = get_pic_src(url)url_groups = allot(pic_urls, processors)for group in url_groups:pool.apply_async(_coroutine_crawler, args=(group, concurrency))pool.close()pool.join()

执行爬虫 :

mixed_process_coroutine_crawler(4,50)

输出:

下载完成,49张图片,耗时:2.56s
下载完成,49张图片,耗时:2.54s
下载完成,49张图片,耗时:2.56s
下载完成,49张图片,耗时:2.62s

效果与多进程 + 多线程 爬虫差不多,但是CPU减少了切换线程上下文的开销,而是对每一个协程任务进行监视回调唤醒。使用IO多路复用的底层原理实现。

分布式采集

关于分布式采集将会单独写一章,使用Map-Reduce+redis来实现分布式爬虫。

轮子们,你们辛苦了

现实生活中的爬虫不止上面那些,但是基本的骨架是一样的,对于特定的网站需要制定特定的采集规则,所以通用的数据采集爬虫很难实现。所以针对某个网站的数据采集爬虫是需要定制的,但是在不同之中包含着许多的相同、重复性的过程,比如说采集流程,或者对请求头部的伪造,数据持久化的处理等,采集框架应运而生。Scrapy就是目前比较成熟的一个爬虫框架。它可以帮助我们大大减少重复性的代码编写,可以更好的组织采集流程。而我们只需要喝一杯咖啡,编写自己的采集规则,让Scrapy去给我们管理各种各样的爬虫,做些累活。如果你是一个爬虫爱好者,那么scrapy是你的不错选择。由于好奇scrapy的实现流程,所以我才开始打开他的源码学习。

有些人觉得scrapy太重,他的爬虫只需要简单的采集,自己写一下就可以搞定了。但如果是大量的爬虫采集呢?怎么去管理这些爬虫呢?怎样才能提高采集效率呀?

Scrapy helps~!!

另外还有另一个Python采集框架:pyspider。国人编写的,cool~

感谢轮子们的父母,还有那些辛苦工作的轮子们,你们辛苦了~

Python爬虫种类、法律、轮子,轮子们,你们辛苦了相关推荐

  1. 关于Python爬虫种类、法律、轮子的一二三

    Welcome to the D-age 对于网络上的公开数据,理论上只要由服务端发送到前端都可以由爬虫获取到.但是Data-age时代的到来,数据是新的黄金,毫不夸张的说,数据是未来的一切.基于统计 ...

  2. python爬虫实战笔记---以轮子哥为起点Scrapy爬取知乎用户信息

    开发环境:python3.5+Scrapy+pycharm+mongodb 思路: 1.选定起始人:选定一个关注数量或粉丝数量多的大佬 2.获取粉丝和关注列表 3.获取列表用户信息 4.获取每位用户粉 ...

  3. Python爬虫可以用来做什么呢?(涉及法律的灰色地带,还希望大家不要尝试。)

    前言 自 TIOBE 榜单创建至今的 20 多年来,本月排行榜的榜首位置首次出现了除 Java 和 C 以外的第三个编程语言--Python.这也就意味着,Java 和 C 的长期霸权已经结束. 大数 ...

  4. 经典的Python爬虫和网络编程面试题

    1.动态加载又对及时性要求很高怎么处理? Selenium+Phantomjs 尽量不使用 sleep 而使用 WebDriverWait 2.分布式爬虫主要解决什么问题? (1)ip (2)带宽 ( ...

  5. 最全python爬虫面试笔试题及答案汇总,三万多字,持续更新,适合新手,应届生

    目录 一些经典的Python爬虫和网络编程面试题... 1 1.动态加载又对及时性要求很高怎么处理?... 1 2.分布式爬虫主要解决什么问题?... 1 3.什么是 URL?... 1 4.pyth ...

  6. Python 爬虫修养-处理动态网页

    Python 爬虫修养-处理动态网页 本文转自:i春秋社区 0x01 前言 在进行爬虫开发的过程中,我们会遇到很多的棘手的问题,当然对于普通的问题比如 UA 等修改的问题,我们并不在讨论范围,既然要将 ...

  7. Python 爬虫进阶一之爬虫框架概述

    综述 爬虫入门之后,我们有两条路可以走. 一个是继续深入学习,以及关于设计模式的一些知识,强化 Python 相关知识,自己动手造轮子,继续为自己的爬虫增加分布式,多线程等功能扩展.另一条路便是学习一 ...

  8. Python爬虫 ---(1)爬虫基础知识

    引言 网络爬虫是抓取互联网信息的利器,成熟的开源爬虫框架主要集中于两种语言Java和Python.主流的开源爬虫框架包括: 1.分布式爬虫框架:Nutch 2.Java单机爬虫框架:Crawler4j ...

  9. Python爬虫十六式 - 第三式:Requests的用法

    Requests: 让 HTTP 服务人类 学习一时爽,一直学习一直爽   Hello,大家好,我是Connor,一个从无到有的技术小白.今天我们继续来说我们的 Python 爬虫,上一次我们说到了 ...

最新文章

  1. JS 二级菜单栏的tab切换
  2. HBase 1.1.2 优化插入 Region预分配
  3. DL之SqueezeNet:SqueezeNet算法的架构详解
  4. 东平谋定农业功能化-农业大健康·万祥军:品牌化精准扶贫
  5. Linux Shell编程(4)——shell特殊字符(上)
  6. .NET 云原生架构师训练营(模块二 基础巩固 REST RESTful)--学习笔记
  7. python编程小案例_用Python3编程写第一个小案例!-Go语言中文社区
  8. 1388C. Uncle Bogdan and Country Happiness
  9. IDEA阅读spring源码并调试
  10. centeros 下载及安装
  11. 计算机测试代码怎么写,常见的电脑检测卡代码对照表大全
  12. mysql堆溢出_MySQL错误1436:线程堆栈溢出,带有简单查询
  13. win10 桌面右键菜单内容修改
  14. 如何求有序数组绝对值最小的数
  15. 华为智能音响2代鸿蒙,99999元!华为全屋智能方案来了:鸿蒙生态是亮点
  16. java实用型:mybatis的好帮手-MybatisCodeHelperPro
  17. C对接国际验证码接口DEMO示例
  18. 科学计算机 logo,电脑上各种标志的源起(一)
  19. JavaScript中linux时间戳与日期的转换
  20. 编写优秀软文的六大技巧

热门文章

  1. uniapp自定义整包更新与热更新
  2. springboot中ElasticSearch入门与进阶:组合查询、聚合查询
  3. 2022-2027年中国商用中央空调行业发展监测及投资战略研究报告
  4. linux安装autossh详细教程,Linux系统入门学习:如何安装autossh
  5. SAP:在互联网时代帮助企业夺回数据
  6. 致青春(诉秋语 韵秋心)
  7. 国睿驰120gb固态硬盘测试软件,小巧极速 国睿驰精睿系列移动SSD评测
  8. brpc搭建、编译和使用
  9. Unity 鱼的游动
  10. Matlab对BPSK与QPSK进行仿真,BPSKQPSK的MATLAB仿真