关于crawler:使用craco对cra项目进行构建优化

批改CRA我的项目的配置应用create-react-app 创立的我的项目默认是无奈批改其外部的webpack配置的,不像vue-cli那样能够通过一个配置文件批改。 尽管有一个eject 命令能够是将配置齐全裸露进去,但这是一个不可逆的操作,同时也会失去CRA带来的便当和后续降级。 如果想要无 eject 重写 CRA 配置,目前成熟的是上面这几种形式 通过 CRA 官网反对的 --scripts-version 参数,创立我的项目时应用本人重写过的 react-scripts 包应用 react-app-rewired + customize-cra 组合笼罩配置应用 craco 笼罩配置这里我抉择的是craco 装置 装置依赖 yarn add @craco/craco复制代码批改pacage.json中的命令 { "scripts":{ "start": "craco start", "build": "craco build", "test": "craco test" }} 复制代码在根目录创立craco.config.js配置文件 /* craco.config.js */module.exports = { // ... webpack: {}, babel: {},}复制代码根底的配置到此实现了,接下来是解决各种配置的笼罩,残缺的 craco.config.js 配置文件构造,能够在 craco 官网的文档中具体查问:Configuration File 。 留神! 目前的craco最新版本v6.4.3仅反对cra4创立的我的项目 构建体积剖析首先引入了webpack-bundle-analyzer 这个插件来剖析一下构建产物的组成 /* craco.config.js */const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer'); module.exports = { webpack: { plugins: [ new BundleAnalyzerPlugin({ analyzerMode: 'server', analyzerHost: '127.0.0.1', analyzerPort: 8888, openAnalyzer: true, // 构建完关上浏览器 reportFilename: path.resolve(__dirname, `analyzer/index.html`), }), ], }}复制代码在应用yarn build命令打包后,就能够失去一个剖析图,蕴含了每个chunk的组成部分。 ...

February 9, 2022 · 3 min · jiezi

Python 爬虫——抖音App视频抓包

APP抓包前面我们了解了一些关于 Python 爬虫的知识,不过都是基于 PC 端浏览器网页中的内容进行爬取。现在手机 App 用的越来越多,而且很多也没有网页端,比如抖音就没有网页版,那么上面的视频就没法批量抓取了吗?答案当然是 No!对于 App 来说应用内的通信过程和网页是类似的,都是向后台发送请求,获取数据。在浏览器中我们打开调试工具就可以看到具体的请求内容,在 App 中我们无法直接看到。所以我们就要通过抓包工具来获取到 App 请求与响应的信息。关于抓包工具有 Wireshark,Fiddler,Charles等。今天我们讲一下如何用 Fiddler 进行手机 App 的抓包。Fiddler 的工作原理相当于一个代理,配置好以后,我们从手机 App 发送的请求会由 Fiddler 发送出去,服务器返回的信息也会由 Fiddler 中转一次。所以通过 Fiddler 我们就可以看到 App 发给服务器的请求以及服务器的响应了。Fiddler 安装配置我们安装好 Fiddler 后,首先在菜单 Tool>Options>Https 下面的这两个地方选上。然后在 Connections 标签页下面勾选上 Allow remote computers to connect,允许 Fiddler 接受其他设备的请求。同时要记住这里的端口号,默认是 8088,到时候需要在手机端填。配置完毕,保存后,一定关掉 Fiddler 重新打开。手机端配置确保手机和电脑在同一个局域网中,我们先看下计算机的 IP 地址,在 cmd 中输入 ipconfig 就可以看到。我电脑用的是无线网,所以 IP 地址为 192.168.1.3。打开手机无线连接,选择要连接的热点。长按选择修改网络,在代理中填上我们电脑的 IP 地址和 Fiddler 代理的端口。如下图所示:保存后,在手机原生浏览器打开 http://192.168.1.3:8008 ,就是上面我们的计算机 IP 和端口。这一步我在夸克浏览器中打开是不行的,一定要到手机自带的浏览器打开。打开后,点击下图链接,下载证书,然后安装证书。电脑端浏览器也需要打开此地址,安装证书,方便以后对浏览器的抓包操作。安装后就万事 OK 了,可以用手机打开 App ,在 Fiddler 上愉快的抓包了。抓包我们打开抖音 App,会发现 Fiddler 上出来很多连接。我们先清空没用的连接信息,然后滑动到某个人的主页上,来查看他发布过的所有视频,同时在 Fiddler 上找到视频链接。经过观察筛选我们可以看出上图就是我们需要的请求地址,这个地址其实是可以在浏览器上打开的,但是我们需要改一下浏览器的User-Agent,我用的是Firefox的插件,打开后和 Fiddler 右边的信息是一致的。我们看下 Fiddler 右边该请求的响应信息。看到返回了一个 JSON 格式的信息,其中aweme_list 就是我们需要的视频地址,has_more=1 表示往上滑动还会加载更多。之后就可以写代码了。代码代码很简单,和我们前几篇讲的一样,直接用 requests 请求相应链接即可。代码仅做为一个简单的例子,仅仅下载当前页面的内容,如果要下载全部的视频,可以根据当次返回 JSON 结果中的 has_more 和 max_cursor 参数构造出新的 URL 地址不断的下载。URL 中的 user_id 可以根据自己要爬取的用户更改,可以通过把用户分享到微信,然后在浏览器中打开链接,在打开的 URL 中可以看到用户的 user_id。import requestsimport urllib.requestdef get_url(url): headers = {‘user-agent’: ‘mobile’} req = requests.get(url, headers=headers, verify=False) data = req.json() for data in data[‘aweme_list’]: name = data[‘desc’] or data[‘aweme_id’] url = data[‘video’][‘play_addr’][‘url_list’][0] urllib.request.urlretrieve(url, filename=name + ‘.mp4’)if name == “main”: get_url(‘https://api.amemv.com/aweme/v1/aweme/post/?max_cursor=0&user_id=98934041906&count=20&retry_type=no_retry&mcc_mnc=46000&iid=58372527161&device_id=56750203474&ac=wifi&channel=huawei&aid=1128&app_name=aweme&version_code=421&version_name=4.2.1&device_platform=android&ssmix=a&device_type=STF-AL10&device_brand=HONOR&language=zh&os_api=26&os_version=8.0.0&uuid=866089034995361&openudid=008c22ca20dd0de5&manifest_version_code=421&resolution=1080*1920&dpi=480&update_version_code=4212&_rticket=1548080824056&ts=1548080822&js_sdk_version=1.6.4&as=a1b51dc4069b2cc6252833&cp=dab7ca5f68594861e1[wIa&mas=014a70c81a9db218501e1433b04c38963ccccc1c4cac4c6cc6c64c’)运行后就可以得到视频列表:有任何疑问,欢迎加我微信交流。 ...

January 22, 2019 · 1 min · jiezi

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

Welcome to the D-age对于网络上的公开数据,理论上只要由服务端发送到前端都可以由爬虫获取到。但是Data-age时代的到来,数据是新的黄金,毫不夸张的说,数据是未来的一切。基于统计学数学模型的各种人工智能的出现,离不开数据驱动。数据采集、清洗是最末端的技术成本,网络爬虫也是基础采集脚本。但是有几个值得关注的是:对于实时变化的网络环境,爬虫的持续有效性如何保证数据采集、清洗规则的适用范围数据采集的时间与质量–效率爬与反爬的恩怨爬虫的法律界限法律的边界,技术无罪对于上面几个关注点,我最先关注的便是爬虫的法律界限 ,我曾经咨询过一个律师:Q: 老师,我如果用爬虫爬取今日头条这种类型网站的千万级公开数据,算不算违法呢?A: 爬取的公开数据不得进行非法使用或者商业利用简单的概括便是爬虫爬取的数据如果进行商业出售或者有获利的使用,便构成了“非法使用”。而一般的爬虫程序并不违法,其实这是从法律专业的一方来解读,如果加上技术层面的维度,那么应该从这几方面考虑:爬取的数据量爬取数据的类型(数据具有巨大的商业价值,未经对方许可,任何人不得非法获取其数据并用于经营行为)爬取的数据用途 (同行竞争?出售?经营?分析?实验?…)是否遵循网站的robots.txt 即 机器人协议爬取行为是否会对对方网站造成不能承受的损失(大量的爬取请求会把一个小型网站拖垮)其实爬虫构成犯罪的案例是开始增多的,相关新闻:当爬虫遇上法律会有什么风险?程序员爬虫竟构成犯罪?爬虫相关法律知识如果你的上级或公司要求你爬取某些网站的大量公开数据,你会怎么办呢?可以参考第2条新闻。法律矛盾点关键在于前面考虑的前三点,如果是个人隐私数据,是不能爬取的,如果是非公开数据,是不能爬取的,而对于其他大量的公开数据爬取,看人家查不查的到你,要不要起诉你。技术在你的手上,非法与否在于你怎么去用。最好的爬取道德原则是:减少并发请求延长请求间隔不进行公开出售数据遵循网站 robots协议当然,反爬最有效的便(目的均在于拦截爬虫进入网站数据范围)是:要求用户密码+验证码加密数据js混淆css混淆针对IP请求频率封锁针对cookie、session单个账户请求频率封锁单日请求次数对关键数据进行拆分合并对爬虫投毒(返回假数据)完善robots.txt识别点击九宫图中没有包含xxx的图片等(终极验证码)设置黑白名单、IP用户组等工欲善其事针对网站的公开数据进行爬取,我们一般都要先对网站数据进行分析,定位,以确定其采集规则,如果网站设置了访问权限,那么便不属于我们的爬虫采集范围了:)分析好采集规则,写好了采集数据持久化(存入数据库、导出为word、excel、csv、下载等)的相关代码,整个爬虫运行正常。那么怎样才能提高采集速度呢?多进程采集多线程采集异步协程采集多进程 + 多线程采集多进程 + 异步协程采集分布式采集异步爬虫是同步爬虫的升级版,在同步爬虫中,无论你怎么优化代码,同步IO的阻塞是最大的致命伤。同步阻塞会让采集任务一个个排着长队领票等待执行。而异步采集不会造成IO阻塞,充分利用了IO阻塞任务的等待时间去执行其他任务。在IO 模型中,只有IO多路复用(I/O multiplexing){在内核处理IO请求结果为可读或可写时调用回调函数} 不阻塞 “内核拷贝IO请求数据到用户空间”这个过程,实现异步IO操作。同步爬虫一般的同步爬虫,我们可以写一个,(以爬取图片网站图片为例),我们来看看其下载该网址所有图片所花费的时间:以下代码为后面多个例程的共同代码:#coding:utf-8import timefrom lxml import etreeimport 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 % n groups = [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 Pooldef 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 randomfrom threading import Threaddef run_multithread_crawler(pic_urls:list,threads:int): begin = 0 start = time.time() while 1: _threads = [] urls = pic_urls[begin:begin+threads] if not urls: break for 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 += threads end = 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)并发线程数太多会让我们的系统开销越大,使程序花费时间越长,同时也会增大目标网站识别爬虫机器行为的几率。因此设置好一个适当的线程数以及爬取间隔是良好的爬虫习惯。执行多线程爬虫,设置线程数为50multithreads_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 asynciofrom asyncio import Semaphorefrom 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): # 异步协程爬虫,最大并发请求数concurrency tasks = [] 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 aiofilesasync 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: break await 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感谢轮子们的父母,还有那些辛苦工作的轮子们,你们辛苦了本文所用代码 均在GitHub上,地址:这里 ...

November 14, 2018 · 2 min · jiezi