关于网页爬虫:监控网页内容变化语音播放更新内容

新装置的中文版Windows11零碎,发现语音朗诵时只反对英文,无奈朗诵中文内容。同样的软件在win10下是能够失常朗诵中文的,因而判断为零碎语言设置问题。分享一下具体语言设置和中文朗诵测试方法。1、在零碎设置窗口,找到并关上“工夫和语言”,抉择子项“语言和区域”。如果是英文版零碎,则点击【增加语言】增加简体中文。2、点击”中文简体“栏左边的菜单项,关上【语言选项】。 3、在语音栏中,点击【下载】按钮下载语音。实现下载后需重启电脑才会失效(只管零碎没有提醒重启)。 4、在浏览器项目管理中,设置好监控内容更新揭示和语音播报。 5、测试新闻监控,能够弹出最新新闻标题的主动播报中文内容了。

March 26, 2022 · 1 min · jiezi

关于网页爬虫:常见的反爬虫技术有哪些如何防止别人爬自己的网站

搜索引擎能够通过爬虫抓取网页信息,同时也有很多企业通过爬虫获取其余平台的信息用于数据分析或者内容优化,然而对于本身网站有些页面或者信息并不心愿被爬虫抓取,那咱们如何来实现反爬虫技术呢?如何避免他人爬本人的网站呢? 爬虫不仅会占用大量的网站流量,造成有真正需要的用户无奈进入网站,同时也可能会造成网站要害信息的透露,所以为了防止这种状况产生网站开发工程师必须把握相应的反爬虫技术。上面为大家提供几种可行的反爬虫计划: 1、通过user-agent来管制拜访 user-agent可能使服务器辨认出用户的操作系统及版本、cpu类型、浏览器类型和版本。很多网站会设置user-agent白名单,只有在白名单范畴内的申请能力失常拜访。所以在咱们的爬虫代码中须要设置user-agent伪装成一个浏览器申请。有时候服务器还可能会校验Referer,所以还可能须要设置Referer(用来示意此时的申请是从哪个页面链接过去的)。 2、通过IP来限度 当咱们用同一个ip屡次频繁拜访服务器时,服务器会检测到该申请可能是爬虫操作。因而就不能失常的响应页面的信息了。当然这种反爬虫技术能够通过应用IP代理池来反反爬虫。网上就有很多提供代理的网站。 3、设置申请距离 个别爬虫抓取网站时会制订相应的爬虫策略,然而有些歹意的爬虫会不间断的攻打某个网站,面对这种状况,咱们能够通过设计申请距离来实现反爬虫,防止在爬虫短时间内大量的拜访申请影响网站的失常运行。 4、自动化测试工具Selenium Web应用程序测试的Selenium工具。该工具能够用于单元测试,集成测试,零碎测试等等。它能够像真正的用户一样去操作浏览器(包含字符填充、鼠标点击、获取元素、页面切换),反对Mozilla Firefox、Google、Chrome、Safari、Opera、IE等等浏览器。 5、参数通过加密 某些网站可能会将参数进行某些加密,或者对参数进行拼接发送给服务器,以此来达到反爬虫的目标。这个时候咱们能够试图通过js代码,查看破解的方法。或者能够应用"PhantomJS",PhantomJS是一个基于Webkit的"无界面"(headless)浏览器,它会把网站加载到内存并执行页面上的JavaScript,因为不会展现图形界面,所以运行起来比残缺的浏览器更高效。 6、通过robots.txt来限度爬虫 robots.txt是一个限度爬虫的标准,该文件是用来申明哪些货色不能被爬取。如果根目录存在该文件,爬虫就会依照文件的内容来爬取指定的范畴。例如大家能够尝试方位淘宝的robots.txt文件:https://error.taobao.com/robo...。能够看到淘宝对爬虫拜访的限度。“User-agent: *”示意禁止所有爬虫拜访。 爬虫与反爬虫是互联网开发工程师之间的斗智斗勇。作为网站开发者既要把握爬虫的技术,还要更进一步去理解如何实现反爬虫。 感激关注~

March 9, 2021 · 1 min · jiezi

关于网页爬虫:抖音-xgorgon-0408-数据加密算法-hook-逆向分析记录抖音接口抖音API

抖音xgorgon算法用ollvm混同了,次要是流程平坦化,流程混同和运算替换。X-Gorgon是对cookie,X-SS-STUB,X-Khronos,Url进行混合加密之后的参数。这里也辨别状况,有些接口只有url和X-Khronos参加接口加密,有些是url,X-Khronos,X-SS-STUB参加接口加密,有些则是所有都进行接口加密。  概述抖音版本外面加了好几个算法,有as,cp(晚期就这两个),mas,X-Gorgon,X-SS-STUB,X-Khronos算法,很多要害key之间有互相关联,只有有一个环节算错了,就会申请不到数据。目前版本的抖音加了很多的验证,及代码混同,难度偏大。 初探抖音的签名算法在libcms.so中,在JNI_Onload中动静注册jni函数。算法用ollvm混同了,次要是流程平坦化,流程混同和运算替换次要用到一些逆向工具IDA,Xposed框架 钻研8.0版本之后的算法次要是X-Gorgon和X-SS-STUB.之后通过抓包抖音接口,查看Java层,so层代码,剖析如下原理。 X-SS-STUB是post申请时body局部的md5值,然而在为空的状况下,有时候不参加加密,有时候参加加密,具体接口须要具体分析X-Khronos比较简单就是一个unix工夫戳X-Gorgon是对cookie,X-SS-STUB,X-Khronos,Url进行混合加密之后的参数。这里也辨别状况,有些接口只有url和X-Khronos参加接口加密,有些是url,X-Khronos,X-SS-STUB参加接口加密,有些则是所有都进行接口加密。具体接口具体分析剖析明天有空分享一下抖音的加密算法,作为领有宏大用户量的APP,其通信协议加密的强度必定是不弱的,要害算法被VM,只能动态分析去了解。咱们通过抓包剖析,申请的URL上带有AS、CP两个加密字段,这两个字段是晚期版本算法,后又陆续增加了MAS、X-GORGON算法。咱们明天先对AS、CP两个字段进行剖析,这个只能通过动静调试去跟踪加密过程。首先咱们通过工具调试定位到函数 - [IESAntiSpam testForAlert:msg:]定位的具体过程疏忽……,进入持续调试后发现调用SUB_102E3345函数进行加密排序1.整顿剖析流程 1.工夫戳转十六进制 2.将工夫戳排序俩次, a1 v3 是排序key sprintf(byte_102323F30, "%08x", a1); sprintf(byte_102323F3A, "%08x", v3); 3.将url参数用MD5加密一次或俩次依据工夫戳&运算 4.将第一次排序后果写入前16位地址加一写入(从1插入),隔一位插入,前边拼a1 5.将第二次排序后果写入后16位(从0插入)后边拼e1 2.后果排序 a1d5b43se234dccea7 456dcd5s2320cf3e1 &cp=456fcd5s2320cfs3e1&as=a1d5b43se234dccea7 拼接实现后就能够申请了前期版本增加了mas算法和最新的X-gorgon算法,目前最新系列版本算法如果须要理解的话能够交换。3.流程详述那么咱们就可能更加确信header里的x-gorgon对它进行了一次签名,所以咱们间接jadx上手浏览一波反编译后的代码,这里我间接搜寻了x-gorgon关键字,列出了以下后果:那么咱们就可能更加确信header里的x-gorgon对它进行了一次签名,所以咱们间接jadx上手浏览一波反编译后的代码,这里我间接搜寻了x-gorgon关键字,列出了以下后果:这里我抉择了hashMap.put("X-Gorgon", a3);这一行,跳转进去咱们来剖析一下它的代码        这里咱们看到有一个它的值是来自a3,a3则是通过String a3 = a.a(com.ss.sys.ces.a.leviathan(i, currentTimeMillis, a.a(a2 + str4 + str5 + str6)));这行代码进行获取到的后果,咱们看到它传了4个参数,咱们来认真看一下这4个参数具体都是什么内容:        a2起源:            String b2 = tt.d(str);            d.a(b2);        str它就是该办法传进来的参数,咱们前面能够通过hook形式来获取它的具体内容,而它会执行tt.d()、d.a() 进行2次操作,咱们对其tt.d()跟进去                咱们看到它对这个字符串进行了取 ? 和 # 两头值,狐疑是url,如果是url证实它只是取了url前面的参数,那么持续看它的下一个办法:d.a()         咱们看到这里就是进行了MD5签名取值,那么a2剖析到此结束,咱们持续剖析第2个参数        str4起源:       这里非常简单,它就是枚举传进来的第二个参数map,判断如果有X-SS-STUB这个值的话就获取,反之则填充32个0,那么咱们抓包发现并没有X-SS-STUB这个参数,实际上如果咱们的包是POST的话它就会有,实际上它就是POST数据的一个MD5签名值。        str5起源:str5也非常简单,也是枚举map外面有没有COOKIE,如果有就把COOKIE进行MD5,那么该参数也到此结束了        str6起源: String c2 = tt.e(str3);if (c2 != null && c2.length() > 0) {     str6 = d.a(c2);     StcSDKFactory.getInstance().setSession(c2);}        这里咱们记得str3是cookie,它执行了tt.e(str3) 办法获取一个返回值,如果它不是空同样给这个返回值md5,那么咱们跟进去看一下它是做了什么解决:        这里咱们看到它是枚举了cookie外面有没有sessionid这个值,如果有就取出来,那么str6到此结束        参数整顿:            a2 = md5(url) 疑似对网址的参数进行md5            str4 = x-ss-stub,只有post时才无效,否则是32个0            str5 = md5(cookie)  对cookie进行md5            str6 = md5(cookie['sessionid'])    对cookie外面对sessionid进行md5,否则也是32个0        咱们整顿完了这4条参数后,持续剖析,它将这4个参数进行了字符串合并,接着执行 a.a(a2+str4+str5+str6),咱们跟进去看看外面做了什么操作        咱们看到它这里循环了总长度/2次,每次都是把  str[i] 转换成十进制左移4,而后加上 str[i+1] 都一个简略运算,并返回后果,也就是原本是4个32位(128位)而后通过加密后缩短成了64位长度。最初它执行了com.ss.sys.ces.a.leviathan(i, currentTimeMillis, a.a(a2 + str4 + str5 + str6))进行计算,咱们看到它还传了2个参数,i和currentTimeMillis,咱们往前能够看到 i是-1,而currentTimeMillis是以后都十位工夫戳。            最初把计算好都byteArray通过位移转换成了string类型,并put到map外面,那么咱们也分明到看到,k-khronos也就是刚刚到currentTimeMillis工夫戳了。咱们发现因为om.ss.sys.ces.a.leviathan是在so层到libcms.so文件,并且外面有大量到混同就没有再度剖析。咱们能够通过xposed或者unidbg到办法进行调用。 ...

November 30, 2020 · 1 min · jiezi

关于网页爬虫:爬虫基本功就这么点

文章分三个个局部 两个爬虫库requests和selenium如何应用html解析库BeautifulSoup如何应用动静加载的网页数据用requests怎么抓两个爬虫库requests假如windows下装置好了python和pip。 上面用pip装置爬虫库requests 如果提醒pip版本低,不倡议降级,降级后可能python自身版本低,导致pip指令报错。进入Python命令行验证requests库是否可能应用 看到import requests和requests.get函数都没有报错,阐明装置胜利能够开发咱们的第一个爬虫程序了! 将代码文件命名为test.py,用IDEL关上。 最简略的爬虫就这么几行! 引入requests库,用get函数拜访对应地址,断定是否抓取胜利的状态,r.text打印出抓取的数据。而后菜单栏点击Run->Run Module 会弹出Python的命令行窗口,并且返回后果。咱们拜访的是腾讯公布新冠肺炎疫情的地址 如果没有IDEL,间接cmd命令行运行依照上面执行 seleniumselenium库会启动浏览器,用浏览器拜访地址获取数据。上面咱们演示用selenium抓取网页,并解析爬取的html数据中的信息。先装置selenium 接下来装置解析html须要的bs4和lxml。 装置bs4 装置lxml 要确保windows环境变量path的目录下有chromedriver 我d盘的instantclient_12_2曾经加到path里了。所以chromedriver解压到这个目录。chromedriver不同的版本对应Chrome浏览器的不同版本,开始我下载的chromedriver对应Chrome浏览器的版本是71-75(图中最上面的),我的浏览器版本是80所以从新下载了一个才好使。 代码如下 Python执行过程中会弹出 浏览器也主动启动,拜访指标地址 IDEL打印后果如下 HTML解析库BeautifulSoupselenium例子中爬取数据后应用BeautifulSoup库对html进行解析,提取了感兴趣的局部。如果不解析,抓取的就是一整个html数据,有时也是xml数据,xml数据对标签的解析和html是一样的情理,两者都是<tag>来辨别数据的。这种格局的数据结构一个页面一个样子,解析起来很麻烦。BeautifulSoup提供了弱小的解析性能,能够帮忙咱们省去不少麻烦。 应用之前装置BeautifulSoup和lxml。首先代码要引入这个库(参考下面selenium库代码) from bs4 import BeautifulSoup 而后,抓取 r = request.get(url) r.encoding='utf8' html=r.read() #urlopen获取的内容都在html中 mysoup=BeautifulSoup(html, 'lxml') #html的信息都在mysoup中了 假如咱们对html中的如下局部数据感兴趣 <data> <day>20200214</day> <id>1</id> <rank>11</rank> <name>张三</name> </data> <data> <day>20200214</day> <id>4</id> <rank>17</rank> <name>李斯</name> </data> 首先要找到tag标签为<data>的数据,而这类数据不止一条,咱们以两条为例。那么须要用到beautifulsoup的find_all函数,返回的后果应该是两个<data>数据。当解决每一个<data>数据时,外面的<id><name>等标签都是惟一的,这时应用find函数。 mysoup=BeautifulSoup(html, 'lxml') data_list=mysoup.find_all('data') for data in data_list:#list应该有两个元素 day = data.find('day').get_text() #get_text是获取字符串,能够用.string代替 id = data.find('id').get_text() rank = data.find('rank').get_text() name = data.find('name').get_text() #print name 能够print测试解析后果 这是beautifulsoup最简略的用法,find和find_all不仅能够依照标签的名字定位元素,还能够依照class,style等各种属性,以及文本内容text作为条件来查找你感兴趣的内容,十分弱小。 ...

November 21, 2020 · 1 min · jiezi

关于网页爬虫:python-爬虫-简单实现百度翻译

环境python版本号零碎游览器python 3.7.2win7google chrome对于本文本文将会通过爬虫的形式实现简略的百度翻译。本文中的代码只供学习,不容许作为于商务作用。商务作用请返回api.fanyi.baidu.com购买付费的api。若有进犯,立刻删文! 实现思路在网站文件中找到暗藏的收费api。传入api所须要的参数并对其发出请求。在返回的json后果里找到相应的翻译后果。 百度翻译的反爬机制由js算法生成的signcookie检测token暗号在网站文件中找到暗藏的收费api进入百度翻译,轻易输出一段须要翻译的文字。当翻译后果进去的时候,按下F12,抉择到NETWORK,最初点进XHR文件。这个时候,网站文件都曾经加载完了,所以要F5刷新一下。 刷新了之后,咱们就能发现一个以v2transapi?结尾的文件,没错,它就是咱们要找的api接口。让咱们验证一下,点进去文件-preview,咱们就能够在json格局的数据外面找到翻译后果,验证胜利。另外,咱们还须要获取咱们的cookie和token,在之后的反爬机制中咱们须要用到它们,地位如以下。cookie地位: token地位: api信息接口:https://fanyi.baidu.com/v2tra...申请形式:post 申请参数大全 参数介绍from源语言to目标语言query翻译文本sign由js算法生成的签名(反爬)token申请暗号开始写代码导入request和execjs库 import requestsimport execjsrequests HTTP库,用于爬虫execjs 用于调用js代码反反爬虫因为百度翻译有cookie辨认反爬机制,所以咱们设置好咱们刚刚获取到的cookie来进行掩护网络蜘蛛身份。 headers = {'cookie':'请在这里输出你的cookie'}另外,咱们还要设置好token(暗号)。 token = '请在这里搁置你的token'最初只剩下sign反爬机制了,sign是由js算法给译文生成的一个签名。我在网上搜了一下,找到了相应的js算法,分享给大家。 var i = "320305.131321201"function n(r, o) { for (var t = 0; t < o.length - 2; t += 3) { var a = o.charAt(t + 2); a = a >= "a" ? a.charCodeAt(0) - 87 : Number(a), a = "+" === o.charAt(t + 1) ? r >>> a : r << a, r = "+" === o.charAt(t) ? r + a & 4294967295 : r ^ a } return r} function e(r) { var o = r.match(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g); if (null === o) { var t = r.length; t > 30 && (r = "" + r.substr(0, 10) + r.substr(Math.floor(t / 2) - 5, 10) + r.substr(-10, 10)) } else { for (var e = r.split(/[\uD800-\uDBFF][\uDC00-\uDFFF]/), C = 0, h = e.length, f = []; h > C; C++) "" !== e[C] && f.push.apply(f, a(e[C].split(""))), C !== h - 1 && f.push(o[C]); var g = f.length; g > 30 && (r = f.slice(0, 10).join("") + f.slice(Math.floor(g / 2) - 5, Math.floor(g / 2) + 5).join("") + f.slice(-10).join("")) } var u = void 0, l = "" + String.fromCharCode(103) + String.fromCharCode(116) + String.fromCharCode(107); u = null !== i ? i : (i = window[l] || "") || ""; for (var d = u.split("."), m = Number(d[0]) || 0, s = Number(d[1]) || 0, S = [], c = 0, v = 0; v < r.length; v++) { var A = r.charCodeAt(v); 128 > A ? S[c++] = A : (2048 > A ? S[c++] = A >> 6 | 192 : (55296 === (64512 & A) && v + 1 < r.length && 56320 === (64512 & r.charCodeAt(v + 1)) ? (A = 65536 + ((1023 & A) << 10) + (1023 & r.charCodeAt(++v)), S[c++] = A >> 18 | 240, S[c++] = A >> 12 & 63 | 128) : S[c++] = A >> 12 | 224, S[c++] = A >> 6 & 63 | 128), S[c++] = 63 & A | 128) } for (var p = m, F = "" + String.fromCharCode(43) + String.fromCharCode(45) + String.fromCharCode(97) + ("" + String.fromCharCode(94) + String.fromCharCode(43) + String.fromCharCode(54)), D = "" + String.fromCharCode(43) + String.fromCharCode(45) + String.fromCharCode(51) + ("" + String.fromCharCode(94) + String.fromCharCode(43) + String.fromCharCode(98)) + ("" + String.fromCharCode(43) + String.fromCharCode(45) + String.fromCharCode(102)), b = 0; b < S.length; b++) p += S[b], p = n(p, F); return p = n(p, D), p ^= s, 0 > p && (p = (2147483647 & p) + 2147483648), p %= 1e6, p.toString() + "." + (p ^ m)}等等,咱们不是在用python进行爬虫吗?那咱们又不会js代码,怎么调用啊?还好python有着弱小的第三方库,当然也少不了调用js代码的库。调用js代码的库很多,然而自己还是举荐大家应用execjs,简略性能又残缺。 ...

November 14, 2020 · 7 min · jiezi

关于网页爬虫:抖音Api上线包含视频达人直播等

抖音Api上线,蕴含视频、达人、直播等 分割:点击查看联系方式:TiToData 接口列表: 搜寻关键词搜寻用户关键词搜寻话题关键词搜寻视频关键词搜寻音乐关键词搜寻直播关键词搜寻地址关键词搜寻商品关键词综合搜寻 达人达人信息达人视频列表达人直播信息达人商品橱窗 视频视频详情视频评论列表视频带货商品列表 直播直播间信息直播间 弹幕、关注、送礼、点赞 实时查问直播间带货商品列表直播间开播查问 带货带货商品查问带货同款商品视频列表 话题话题详情话题视频列表 榜单明星榜热点榜直播榜今日最热视频榜

October 20, 2020 · 1 min · jiezi

关于网页爬虫:长虹电器旗舰店被薅倒闭背后的风险与防控

10月10日,某大型电商平台上一家名为“长虹厨房电器旗舰店”的商家布告称,因为某推广机构的歹意坑骗,标价60多元的电热水壶,被不到10元钱买下拍了20万单。由此产生微小的损失,甚至有破产倒闭危险,恳请消费者退款。 有网友认为“薅羊毛”不能薅秃了羊,决定申请退款;也有网友观点认为,消费者并无过错,商家理当为本人的行为负责,营销流动中呈现了问题不应该将责任推给消费者,电商平台应该增强没有契约精力的商家增强监管。 这并非电商平台上首次因“薅羊毛”造成电商损失的状况。2019年1月,有网友称拼多多存在重大Bug,只需领取4毛钱就能够充值100元话费。随后,拼多多将所有优惠券的支付形式下架,勾销了用户已支付但未应用的优惠券,并称上海警方已以“网络欺骗”的罪名立案并成立专案组,对涉事订单进行批量解冻。 “薅羊毛”曾经成为电商行业不可接受之重。尤其是组织化、团伙化的“羊毛党”,在每年的双十一、618等电商促销节大肆“薅羊毛”,不仅批量哄抢优惠券、折扣券、积分等,更将消费者心仪的、高性价比的商品“薅”走。数据显示,电商批发企业70%~80%的营销费用会被等“羊毛党”等吞噬。 “薅羊毛”的流程与攻打特色电商“薅羊毛”就是利用业务存在逻辑破绽或技术破绽,通过技术工具,批量争夺本来属于用户的优惠和福利,再转售进来进行获利。这种抢优惠券、秒杀特价商品等行为,不仅侵害了消费者合法利益,更给商家、电商平台带来经济损失。 顶象在长期与黑灰产的攻防中总结出“羊毛党”的作案流程: 首先,“羊毛党”安顿专人去各个电商平台、社群收集优惠、促销、折扣、积分信息,并进行汇总和梳理。 其次,通过相熟流动流程,“羊毛党”剖析流动或业务存在的破绽,进而破解业务逻辑,测试出可能进行批量操作的薅羊毛计划。 而后,“羊毛党”筹备各类工具和材料,如从卡商或黑市购买/租赁的手机号、身份信息等数据,编写各类主动程序软件等。 第四步,通过自动化的注册软件、接码平台、群控工具等,进行虚伪账户的批量注册,或进行批量登录等操作。 第五步,促销正式开时候,“羊毛党”批量操控账号对特价商品、打折券等优惠商品进行哄抢。 最初,将抢到的商品、优惠券等通过社群、电商平台转售进来。 “羊毛党”的防控难点“羊毛党”是一群有打算、有预谋的团伙化,彼此分工明确、单干严密、协同作案,造成一条残缺的产业链。“羊毛党”对电商的各项业务流程十分相熟,很分明需要和风控规定及业务破绽,可能娴熟的使用挪动互联网、云计算、人工智能各种新技术,进行各种危险欺诈操作,由此给电商反欺诈带来诸多新挑战。 并且,电商业务中波及到的优惠零碎、客户零碎等,规定应用上相互重叠,风控流程上很容易互相矛盾,甚至互相打架,这就导致在危险防控上存在若干难点。 综合来看,“薅羊毛”防控有以下难点: 1、秒拨IP、模拟器等业余工具不断更新,新伎俩层出不穷,反抗难度大。 2、“羊毛党”手握大量账号,个体行为非法、群体非法,辨认难度大。 3、业务危险点多,账户、订单等各零碎均有可能呈现破绽,单点防控难度大。 4、业务防护体系中积攒的危险核验数据量不够,更新不及时。 5、攻打起源简单,既有不良用户,又有业余黑灰产,还有可能是歹意同行。 6、业务防护影响店铺业绩,一旦呈现误判,将间接影响网店交易量和排名。 顶象为电商平台提供业余防控体系顶象帮忙电商企业构建自主可控的平安体系,能无效防备“羊毛党”危险。 剖析显示,“羊毛党”有以下特色: 1、IP地址高度对立:失常用户来自四面八方,注册登录操作的IP地址各不相同。而羊毛党们的注册设施和软件通常应用同一个宽带线路接入网络,注册和登录平台的IP地址根本固定,或来自于同一批代理IP。 2、账号注册登录多集中非业务时间段:失常用户个别是在失常作息时间内注册登录操作,一旦呈现问题能够及时分割工作人员解决。而羊毛党则喜爱在劳动时间段操作,此时平台的系统监控会绝对放松,羊毛党批量操作时会占用平台带宽或接口,在非业务时间段操作能够防止引起运营者警觉。 3、账号注册登录行为十分晦涩:失常用户注册登录时,要人工输出用户名、明码、手机号,收到验证码后还要再次手动输出,整个过程不法则且有肯定提早,过程中可能会因为不相熟规定或因为其余事件而耽误停止。而羊毛党应用自动化的软件工具进行账户注册,流程化作业如行云流水般零打碎敲,速度和节奏上是人工速度的数倍。 4、设施特色24小时无变动:失常用户注册登录时,可能坐在椅子上、躺在床上、坐在车上,手机会依据动作进行不同角度的调整,手机的程度高度也会不停的调整。羊毛党应用软件操控批量设施,这些设施大多是搁置在不同机架上,24小时放弃角度和水平线无变动。 依据以上特色和防控难点,顶象全链路防控体系从多个流程节点着手:通过端平安,避免黑灰产获取业务逻辑接口信息、避免被入侵破解;通过对通信链路爱护,避免数据传输过程中被劫持篡改;通过人机辨认技术的验证码,避免机器注册危险;通过设施核验技术,避免多开、模拟器、秒拨IP等危险;针对真人用户的危险操作,通过风控引擎的操作特色和穿插验证,避免异样操作等。由此帮忙电商平台构建一条全流程的纵深防控体系,无效防备电商“薅羊毛”。

October 16, 2020 · 1 min · jiezi

关于网页爬虫:抖音短视频数据集上线

抖音短视频数据集-2019年 抖音短视频数据集反对抖音热搜品牌榜、视频榜、音乐榜、明星榜、好物榜抖音KOL数据,凭借当先的人工智能与大数据,全域洞察5亿+抖音沉闷用户、100万中国最沉闷KOL,100+全域行业浸透。 分割咱们:TiTo数据:抖音采集 快手采集 TikTok Zynn YouTube 字段阐明名称类型阐明user_idstring用户idvidstring视频iduser_nicknamestring用户昵称user_avatarstring用户头像descstring视频形容uristring视频资源定位符categorystring视频分类coverstring视频封面play_addrstring视频播放地址durationint视频时长comment_countint视频评论数digg_countint视频点赞数share_countint视频分享数create_timeint视频公布工夫 补充阐明:1、因为数据具备可复制性,一经购买,不承受任何理由的退款!2、如需局部指定数据样例,可分割微信:ifuxing123,备注 抖音数据集 数据示例:

October 12, 2020 · 1 min · jiezi

关于网页爬虫:爬取前端渲染网站网站vuereact

最近公司写爬虫 然而对于 前端渲染的网站(vue,react) 然而 chromedp selenium等又太重了于是用puppeteer koa2 写了一个 通用服务https://github.com/dollarkillerx/marionettedocker 运行 docker run --name marionette -d -p3000:3000 dollarkiller/marionette:latest简略说一下这个服务的Restful API GET /ssr?q=http://google.com返回 respcode, html body, cookie 均为 指标网站的返回 咱们当初应用Go来调用下这个APIGo的http client 举荐一下我本人写的urllibhttps://github.com/dollarkillerx/urllib httpCode, bytes, err = urllib.Get("http://0.0.0.0:3000/ssr").Querys("q","http://google.com").Byte()

October 7, 2020 · 1 min · jiezi

关于网页爬虫:抖音视频Api接口上线

抖音Api抖音视频Api、抖音爬虫、抖音去水印、抖音视频下载、抖音视频解析 更多信息请查看官网 TiToData海量数据采集每天为客户采集5亿条数据笼罩支流平台:TikTok,Zynn,YouTube,抖音,快手,1688,小红书,拼多多,淘宝,美团,饿了么,淘宝,微博分割vx:ifuxing123接口包含以下模块热门视频、热门达人、热门商品等等视频列表申请地址GET http://api2.titodata.com/api/video/lists申请参数名称必填类型阐明token是string登录令牌create_time_start否int视频公布工夫戳,开始工夫create_time_end否int视频公布工夫戳,完结工夫author_id否int作者idsort_field否string排序字段,默认digg_count,可选项:digg_count、comment_count、share_countsort否string排序,默认desc,可选项:desc、ascpage否int默认第1页,最大100页page_size否int默认20条,最大100条每页min_digg_count否int最小点赞数min_comment_count否int最小评论数min_share_count否int最小分享数min_duration否int最小时长,毫秒为单位返回参数{ "code": 200, "msg": "胜利", "data": { "total": 5000, "per_page": 1, "current_page": 1, "last_page": 100, "data": [ { "user_id": 104255897823, //作者id "vid": 6865534331515964679, //视频id "user_nickname": "人民日报", //作者昵称 "user_avatar": "https:\/\/p3-dy-ipv6.byteimg.com\/aweme\/100x100\/30ed2000aad26be101cad.jpeg?from=4010531038", //作者头像 "desc": "宝贵视频!9年前袁隆平院士和钟南山院士的同台。明天袁老90岁生日,生日快乐,也愿两位都衰弱高兴!", //视频形容 "uri": "v0200fd60000bt3ke2te6v18nvs8q5q0", //视频资源定位id "cover": "http:\/\/p3-dy-ipv6.byteimg.com\/large\/tos-cn-p-0015\/e6240ccea8a84ff5a77b514ccdf00bc7_1598506787.webp?from=2563711402_large", //视频封面 "play_addr_mark": "https:\/\/aweme.snssdk.com\/aweme\/v1\/playwm?video_id=v0200fd60000bt3ke2te6v18nvs8q5q0", //视频播放地址,带水印 "duration": 23840, //视频时长,毫秒 "digg_count": 12119790, //点赞数 "comment_count": 315139, //评论数 "share_count": 76000, //分享数 "create_time": "2020-08-27 13:39:41", //工夫公布工夫 "category": "政企", //分类,26个分类 "tag": "社会" //标签,可选项:搞笑、生存、社会 } ] }}

October 1, 2020 · 1 min · jiezi

关于网页爬虫:从零开始搭建完整的电影全栈系统一数据库设计及爬虫编写

前言:对于题目仿佛有些虚夸,所谓的全栈零碎次要包含数据的爬取,web网站展现,挪动设施App,次要记录学习过程中知识点,以备忘。 **技术栈:1,Scrapy爬虫框架:记录爬虫框架的工作流程,简略爬虫的编写2,Yii框架:用于PC网站、挪动网站以及RESTful Api(为什么不持续用python注入django或者fastapi等框架?次要是目前还不相熟)3,Flutter挪动:用于挪动App搭建** 免责申明:该我的项目不会贮存任何视频资源到服务器,仅作为集体学习过程点滴积攒。 数据库构造:vod_detail次要保留视频信息,play_url用于各个视频的播放地址。这里将视频信息和播放地址离开到不同的表中保留集体感觉更加正当,比方一个电视剧之类的能够有多个剧集播放地址。各个字段阐明见表构造。 vod_detail: -- phpMyAdmin SQL Dump-- version 4.8.5-- https://www.phpmyadmin.net/---- 主机: localhost-- 生成日期: 2020-09-09 10:33:32-- 服务器版本: 5.7.26-- PHP 版本: 7.3.4SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";SET AUTOCOMMIT = 0;START TRANSACTION;SET time_zone = "+00:00";---- 数据库: `film`---- ------------------------------------------------------------ 表的构造 `vod_detail`--CREATE TABLE `vod_detail` ( `id` int(11) NOT NULL, `url` varchar(500) NOT NULL COMMENT '采集的url', `url_id` varchar(100) NOT NULL COMMENT '采集的url通过加密生成的惟一字符串', `vod_title` varchar(255) NOT NULL COMMENT '视频名称', `vod_sub_title` varchar(255) DEFAULT NULL COMMENT '视频别名', `vod_blurb` varchar(255) DEFAULT NULL COMMENT '简介', `vod_content` longtext COMMENT '具体介绍', `vod_status` int(11) DEFAULT '0' COMMENT '状态', `vod_type` varchar(255) DEFAULT NULL COMMENT '视频分类', `vod_class` varchar(255) DEFAULT NULL COMMENT '扩大分类', `vod_tag` varchar(255) DEFAULT NULL, `vod_pic_url` varchar(255) DEFAULT NULL COMMENT '图片url', `vod_pic_path` varchar(255) DEFAULT NULL COMMENT '图片下载保留门路', `vod_pic_thumb` varchar(255) DEFAULT NULL, `vod_actor` varchar(255) DEFAULT NULL COMMENT '演员', `vod_director` varchar(255) DEFAULT NULL COMMENT '导演', `vod_writer` varchar(255) DEFAULT NULL COMMENT '编剧', `vod_remarks` varchar(255) DEFAULT NULL COMMENT '影片版本', `vod_pubdate` int(11) DEFAULT NULL, `vod_area` varchar(255) DEFAULT NULL COMMENT '地区', `vod_lang` varchar(255) DEFAULT NULL COMMENT '语言', `vod_year` varchar(255) DEFAULT NULL COMMENT '年代', `vod_hits` int(11) DEFAULT '0' COMMENT '总浏览数', `vod_hits_day` int(11) DEFAULT '0' COMMENT '一天浏览数', `vod_hits_week` int(11) DEFAULT '0' COMMENT '一周浏览数', `vod_hits_month` int(11) DEFAULT '0' COMMENT '一月浏览数', `vod_up` int(11) DEFAULT '0' COMMENT '顶数', `vod_down` int(11) DEFAULT '0' COMMENT '踩数', `vod_score` decimal(3,1) DEFAULT '0.0' COMMENT '总评分', `vod_score_all` int(11) DEFAULT '0', `vod_score_num` int(11) DEFAULT '0', `vod_create_time` int(11) DEFAULT NULL COMMENT '创立工夫', `vod_update_time` int(11) DEFAULT NULL COMMENT '更新工夫', `vod_lately_hit_time` int(11) DEFAULT NULL COMMENT '最初浏览工夫') ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;---- 转储表的索引------ 表的索引 `vod_detail`--ALTER TABLE `vod_detail` ADD PRIMARY KEY (`id`), ADD UNIQUE KEY `url_id` (`url_id`) COMMENT '惟一 防止抓取过的网址反复采集';---- 在导出的表应用AUTO_INCREMENT------ 应用表AUTO_INCREMENT `vod_detail`--ALTER TABLE `vod_detail` MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;COMMIT;play_url: ...

September 9, 2020 · 10 min · jiezi

关于网页爬虫:掘金15W沸点简单分析二

一、数据预处理与入库获取到了原始数据之后,下一步就是荡涤入库。 1.1 数据模型因为是简略剖析,所以只获取话题、用户、音讯三块内容。具体如下: class Pins(object): """ 沸点 """ msg_id = None # 沸点ID topic_id = None # 话题ID topic_title = None # 话题名称 user_id = None # 用户ID user_name = None # 用户名 msg_content = None # 沸点内容 msg_ctime = None # 沸点创立工夫 msg_digg_count = 0 # 沸点点赞数 msg_comment_count = 0 # 沸点评论数 def __repr__(self): return '<Pins: %s>' % self.msg_id1.2 数据库表创立数据库的话,应用MySQL。因为沸点内容msg_content中含有emoji表情,所以在建表时字符集编码须要应用utf8mb4。 建表SQL语句如下: CREATE SCHEMA `juejin` DEFAULT CHARACTER SET utf8mb4 ;CREATE TABLE `juejin`.`pins` ( `msg_id` VARCHAR(20) NOT NULL COMMENT '音讯ID', `topic_id` VARCHAR(20) NOT NULL COMMENT '主题ID', `topic_title` VARCHAR(16) NOT NULL COMMENT '主题名称', `user_id` VARCHAR(20) NOT NULL COMMENT '用户ID', `user_name` VARCHAR(32) NOT NULL COMMENT '用户昵称', `msg_content` TEXT CHARACTER SET 'utf8mb4' NOT NULL COMMENT '音讯内容', `msg_ctime` VARCHAR(16) NOT NULL COMMENT '音讯创立工夫戳', `msg_digg_count` INT(11) NOT NULL COMMENT '音讯点赞数', `msg_comment_count` INT(11) NOT NULL COMMENT '音讯评论数', `msg_createdate` DATETIME NOT NULL DEFAULT now() COMMENT '音讯创立工夫(同msg_ctime工夫戳)', PRIMARY KEY (`msg_id`));1.3 原始数据的读取及入库接上文,咱们曾经将所有沸点数据保留至json_data文件夹下。只须要将该文件下所有的json文件遍历读取进去,在做简略的解决,而后存入数据库即可。 ...

September 1, 2020 · 2 min · jiezi

关于网页爬虫:强大高效而精简易用的Golang爬虫框架Colly能否取代-Scrapy

前言任何刚接触爬虫编程的敌人可能都相熟或者或多或少理解过基于 Python 异步框架 Twisted 的爬虫框架 Scrapy。Scrapy 倒退了将近 7 年,是爬虫框架中的开山鼻祖,自然而然成为最受欢迎的也是利用最广的爬虫框架。对于 Scrapy 来说,其人造的劣势是反对并发,而且集成了 HTTP 申请、下载、解析、调度等爬虫程序中常见的功能模块,让爬虫工程师只专一于页面解析和制订抓取规定,在过后极大的简化了爬虫开发流程,进步了开发效率。 然而,Scrapy 并不是完满的,它依然有不少毛病。其中,它的模版定制化成为了制约 Scrapy 爬虫我的项目的双刃剑:一方面,Scrapy 形象出了各种必要的模块,包含爬虫(Spider)、抓取后果(Item)、中间件(Middleware)、管道(Pipeline)、设置(Setting)等,让用户能够间接上手依照肯定规定编写本人想要开发的爬虫程序;另一方面,这些高度形象的模块让整个爬虫我的项目显得比拟臃肿,每个爬虫我的项目都须要依照相应的模版生成好几个文件,导致配置、爬虫等治理起来绝对比拟凌乱。而且,Scrapy 在一些非凡场景例如分布式抓取时显得爱莫能助,因而很多高级爬虫工程师甚至须要更改 Scrapy 源码来满足业务要求。 为了解决这些问题,基于动态语言 Golang 的爬虫框架 Colly 在 2017 年末诞生了。尽管它的名气和受欢迎水平还不迭 Scrapy,但在试用之后我发现它有一个最特地的劣势:简略(Easiness)。依据 Colly 官网上的个性介绍,它有清新的 API(Clean API),疾速(Fast),人造反对分布式(Distributed),同步/异步/并行抓取(Sync/async/parallel scraping),丰盛的扩大(Extensions),以及更多个性。作者在简略的看了 Colly 的文档之后,尝试用 Colly 编写一些绝对简略的爬虫程序,发现编写结束后,每个网站的代码只蕴含一个文件。简而言之,它相当轻量级,不须要特地多的冗余代码就能够实现本人想要的逻辑。 下图是 Colly 和 Scrapy 在 Github 的 Star 数比照。能够看到 Colly 倒退较晚,star 数不到 Scrapy 的三分之一,但还在高速增长当中。本文将着重介绍这个年老而弱小的爬虫框架: Colly。 动态语言 GolangColly 是基于动态语言 Golang 开发的。家喻户晓,Golang 也是一个比拟年老的语言,仅有 13 年历史(相较而言,Python 有将近 30 年历史)。Golang 人造反对并发(Concurrency),要起一个协程(Coroutine),只须要在调用函数前加一个 go 关键词即可,非常简单。当然,Golang 还有其余很棒的个性,例如通道(chan)。不过对于爬虫来说反对并发是十分重要的,因为一个高效的爬虫须要尽可能多的占用网络带宽资源,Golang 的并发个性给编写爬虫框架来说带来了劣势。反观 Python,如果要实现并发的话须要做很多工作,包含利用 asyncio 库和从 JavaScript ES7 借鉴过去的 async/await 语法,并不是很直观。 ...

August 28, 2020 · 2 min · jiezi

关于网页爬虫:nodejs实现爬虫

Node.js实现爬虫什么是爬虫? 网络爬虫(又被称为网页蜘蛛,网络机器人,在FOAF社区两头,更常常的称为网页追赶者),是一种依照肯定的规定,主动地抓取万维网信息的程序或者脚本。另外一些不常应用的名字还有蚂蚁、主动索引、模拟程序或者蠕虫。大多数爬虫都是按“发送申请”-"获取页面"-"解析页面"-"抽取并贮存内容"这样的流程来进行,这其实也是模仿了咱们应用浏览器获取网页信息的过程。 所须要的模块puppeteer,下载puppeteer模块,用于Node.js爬虫mongoose,下载mongoose模块,用于连贯数据库,将爬取的数据传入数据库中。//下载puppeteernpm install puppeteer//下载mongoosenpm install mongoose正式开始 在这里咱们爬取豆瓣的电影信息来进行测试哦! 看到下面电影信息列表,是不是有种爬取列表信息的激动?好,咱们先关上开发者选项,查看该列表信息。 咱们能够看到每个li标签的data-xxx属性中都带有该电影项的信息,咱们通过获取到该信息,并且将其输入到某界面或存储到数据库中,这就实现了爬虫多种用处的一小部分。话不多说咱们来上代码。 应用爬虫获取界面电影列表的信息//引入puppeteer模块const puppeteer = require('puppeteer');//爬取热门电影信息,的网址const url = 'https://movie.douban.com/cinema/nowplaying/beijing/';//因为要申请信息,这里咱们退出asyncmodule.exports = async () => { //1. 关上浏览器 const browser = await puppeteer.launch({ args: ['--no-sandbox'], // headless: false //以无头浏览器的模式关上浏览器,没有界面显示,在后盾运行的 }); //2. 创立tab标签页 const page = await browser.newPage(); //3. 跳转到指定网址 await page.goto(url, { waitUntil: 'networkidle2' //期待网络闲暇时,在跳转加载页面 }); //4. 期待网址加载实现,开始爬取数据 //开启延时器,延时2秒钟在开始爬取数据 await timeout(); let result = await page.evaluate(() => { //对加载好的页面进行dom操作 //所有爬取的数据数组 let result = []; //获取所有热门电影的li,这里能够关上开发者选项进行标签的筛选 const $list = $('#nowplaying>.mod-bd>.lists>.list-item'); //这里咱们只取8条数据 for (let i = 0; i < 8; i++) { const liDom = $list[i]; //电影题目 let title = $(liDom).data('title'); //电影评分 let rating = $(liDom).data('score'); //电影片长 let runtime = $(liDom).data('duration'); //导演 let directors = $(liDom).data('director'); //主演 let casts = $(liDom).data('actors'); //豆瓣id let doubanId = $(liDom).data('subject'); //电影的详情页网址 let href = $(liDom).find('.poster>a').attr('href'); //电影海报图 let image = $(liDom).find('.poster>a>img').attr('src'); //将爬取到的数据退出进该数组中 result.push({ title, rating, runtime, directors, casts, href, image, doubanId }) } //将爬取的数据返回进来 return result; }) console.log(result); //遍历爬取到的8条数据 for (let i = 0; i < result.length; i++) { //获取条目信息 let item = result[i]; //获取电影详情页面的网址 let url = item.href; //跳转到电影详情页 await page.goto(url, { waitUntil: 'networkidle2' //期待网络闲暇时,在跳转加载页面 }); //爬取其余数据 let itemResult = await page.evaluate(() => { let genre = []; //类型 const $genre = $('[property="v:genre"]'); for (let j = 0; j < $genre.length; j++) { genre.push($genre[j].innerText); } //简介 const summary = $('[property="v:summary"]').html().replace(/\s+/g, ''); //上映日期 const releaseDate = $('[property="v:initialReleaseDate"]')[0].innerText; //给单个对象增加两个属性 return { genre, summary, releaseDate } }) // console.log(itemResult); //在最初给以后对象增加三个属性 //在evaluate函数中没方法读取到服务器中的变量 item.genre = itemResult.genre; item.summary = itemResult.summary; item.releaseDate = itemResult.releaseDate; } console.log(result); //5. 敞开浏览器 await browser.close(); //最终会将数据全副返回进来 return result;}function timeout() { return new Promise(resolve => setTimeout(resolve, 2000))}将爬取的数据存入进数据库创立数据库连贯 在此时咱们引入mongoose,进行数据库的连贯。话不多说上代码。 ...

August 20, 2020 · 2 min · jiezi

关于网页爬虫:Graphviz-安装配置的问题

明天要下载Graphviz 图形软件,依照网上的教程到官网(Graphviz )上下载,却发现页面和教程上都不一样,也不晓得点哪个比拟好,通过一番致力,找到了文件一份,先收费分享给大家。链接:网盘链接 提取码:6666 再接下来的装置中,依照网络上常见的各种装置步骤,最初在cmd中跑,却呈现''dot'' 不是外部命令之类的提醒,配置好装置环境当前重启也没方法解决问题。最初眉头一皱;计上心来,将其调整到环境变量的最下面轻松解决问题。如图:兄弟姐妹们,如果对你们有用,不要遗记点个赞哟~~~CSDN的网名为:叫我jiajia CSDN链接

August 19, 2020 · 1 min · jiezi

关于网页爬虫:爬虫管理平台Crawlab-社区版-v050发布

前言本次更新包含几个局部: 爬虫市场批量操作数据库底层优化更新日志性能 / 优化爬虫市场. 容许用户下载开源爬虫到 Crawlab.批量操作. 容许用户与 Crawlab 批量交互,例如批量运行工作、批量删除爬虫等等.迁徙 MongoDB 驱动器至 MongoDriver.重构优化节点逻辑代码.更改默认 task.workers 至 16.更改默认 nginx client_max_body_size 为 200m.反对写日志到 ElasticSearch.在 Scrapy 页面展现谬误详情.删除挑战页面.将反馈、免责申明页面挪动到顶部.Bug 修复修复因为 TTL 索引未创立导致的日志不过期问题.设置默认日志过期工夫为 1 天.task_id 索引没有创立.docker-compose.yml 修复.修复 404 页面.修复无奈先创立工作节点问题.参考Github: https://github.com/crawlab-te...Demo: https://crawlab.cn/demo社区如果您感觉 Crawlab 对您的日常开发或公司有帮忙,请加作者微信 tikazyq1 并注明 "Crawlab",作者会将你拉入群。欢送在 Github 上进行 star,以及,如果遇到任何问题,请随时在 Github 上提 issue。另外,欢迎您对 Crawlab 做开发奉献。

July 19, 2020 · 1 min · jiezi

关于网页爬虫:虎扑社区论坛数据爬虫分析报告

原文:http://tecdat.cn/?p=4115 以下是摘自虎扑的官网介绍:虎扑是为年老男性服务的业余网站,涵盖篮球、足球、F1、NFL等赛事的原创新闻专栏视频报道,领有大型的生存/影视/电竞/汽车/数码网上交换社区,聊体育谈趣味尽在虎扑。 二、数据阐明应用的数据起源: 2018/1/1~1/19 两周半内虎扑论坛步行街各子版块的所有帖子,去除关注度极低的帖子,总数为3.3W+; 上述3.3W+篇帖子中主干道版块的回复用户的个人信息,去重后用户总数为2.3W+。 三、虎扑论坛剖析1.各版块活跃度剖析 在虎扑社区由10个分论坛,380个子版块形成,日发帖量达到70W+,其中哪一个最沉闷呢?通过比照每个版块发帖量,能够发现,在大的分论坛中 【配备论坛】的发帖量最高,其次是【步行街】和【NBA】;在380个子板块中,【静止配备】]和【交易区】最热门,其次是【步行街主干道】和【湿乎乎的话题】;其中,【配备论坛】的发帖占比近7成,远高于其余,而【步行街】又高于【NBA】两倍。 能够看出,交易属性和社交属性是虎扑维持社区热度和用户的活跃度的要害。 各大分论坛发帖量占比 各子版块发帖量占比 2.步行街发帖内容分析除了交易版块,【步行街】和【NBA】是虎扑活跃度最高的版块,【NBA论坛】的话题个别围绕赛事开展,而【步行街】的社交属性比拟强,通过对【步行街】的剖析,能够看到在体育赛事之余,虎扑的会员都在关注些什么。 ... 四、虎扑用户剖析1.用户性别 .... 2.用户所在地 ..... 3. 用户的静止喜好 ...... 4.用户日停留时长 .... 五、剖析总结总体上看,虎扑会员以年老的男性和在校大学生为主,他们喜爱篮球等体育运动,关注NBA,英超等球类联赛;但在步行街社区,他们的话题不仅限于体育,八卦热点,情感征询等都是他们关注的内容;并且他们每天在虎扑停留时间较长,能够说虎扑的内容很好的抓住了目前的用户群体。

July 18, 2020 · 1 min · jiezi

关于网页爬虫:虎扑社区论坛数据爬虫分析报告

原文:http://tecdat.cn/?p=4115 以下是摘自虎扑的官网介绍:虎扑是为年老男性服务的业余网站,涵盖篮球、足球、F1、NFL等赛事的原创新闻专栏视频报道,领有大型的生存/影视/电竞/汽车/数码网上交换社区,聊体育谈趣味尽在虎扑。 二、数据阐明应用的数据起源: 2018/1/1~1/19 两周半内虎扑论坛步行街各子版块的所有帖子,去除关注度极低的帖子,总数为3.3W+; 上述3.3W+篇帖子中主干道版块的回复用户的个人信息,去重后用户总数为2.3W+。 三、虎扑论坛剖析1.各版块活跃度剖析 在虎扑社区由10个分论坛,380个子版块形成,日发帖量达到70W+,其中哪一个最沉闷呢?通过比照每个版块发帖量,能够发现,在大的分论坛中 【配备论坛】的发帖量最高,其次是【步行街】和【NBA】;在380个子板块中,【静止配备】]和【交易区】最热门,其次是【步行街主干道】和【湿乎乎的话题】;其中,【配备论坛】的发帖占比近7成,远高于其余,而【步行街】又高于【NBA】两倍。 能够看出,交易属性和社交属性是虎扑维持社区热度和用户的活跃度的要害。 各大分论坛发帖量占比 各子版块发帖量占比 2.步行街发帖内容分析除了交易版块,【步行街】和【NBA】是虎扑活跃度最高的版块,【NBA论坛】的话题个别围绕赛事开展,而【步行街】的社交属性比拟强,通过对【步行街】的剖析,能够看到在体育赛事之余,虎扑的会员都在关注些什么。 ... 四、虎扑用户剖析1.用户性别 .... 2.用户所在地 ..... 3. 用户的静止喜好 ...... 4.用户日停留时长 .... 五、剖析总结总体上看,虎扑会员以年老的男性和在校大学生为主,他们喜爱篮球等体育运动,关注NBA,英超等球类联赛;但在步行街社区,他们的话题不仅限于体育,八卦热点,情感征询等都是他们关注的内容;并且他们每天在虎扑停留时间较长,能够说虎扑的内容很好的抓住了目前的用户群体。

July 17, 2020 · 1 min · jiezi

关于网页爬虫:URL编码问题

url编码起因咱们都晓得url的模式中对于query子段是以?开始的key=value对,每一对之间以&分隔开。那么就有一个问题:如果在key=value对中的key或者value中含有'='或者'&',如:ke&y=value,则在url解析过程中就会产生谬误。进一步的,不只是query子段,在url后面的门路字段中,如果一个路径名中含有'/'或者'?'等字符会怎么样呢?为解决这类问题,就有了url编码问题。 url编码由RFC3986文档中规定,url中只容许应用大小写英文字母,阿拉伯数字和某些标点符号。更精确来说只容许: A-Z,a-z,0-9,-_.~四个特殊字符,也被称为非保留字符以及! * ’ ( ) ; : @ & = + $ , / ? [ ] # 作为保留字符 这里解释一下所谓保留字符和非保留字符的意义,保留字符是指url定义中容许应用的可能会被用作delimiter(分界符)的字符,所以保留下来;而非保留字符则是指在url定义中容许应用的没有保留目标的字符。 所以非保留字符不是保留字符的补集,它只是url容许的字符中不是保留字符的局部! 编码规定url中所应用的编码为:percent-encode,即百分号编码。这种编码模式非常简略,即应用%再加上字符所对应的两位十六进制数值,如:' '空格符对应的就是'%20'。 那么非保留字符和保留字符以外的字符(即ASCII码集中除两者以外的字符),如果应用在url中,就须要进行百分号编码。保留字符应用在delimiter以外的场合也要进行百分号编码。补充一开始对url进行规定时,只思考了英文的状况,所用字符都限度在ASCII字符集中,即url是ASCII编码的。那么对于其它语言如中文,日文等,该如何进行解决呢? RCF3629中倡议对这些ASCII字符集以外的字符先进行UTF-8编码,而后再把对应的UTF-8编码写为percent-encode的模式。 发散再联合python的urllib包,易知在urllib.parse模块中的quote与urlencode函数中都有对字符串进行percent-encode,并且还主动蕴含了对于非ASCII字符进行urf-8编码再percent-encode的行为。

July 17, 2020 · 1 min · jiezi

关于网络爬虫如何避免环路死循环与避免陷阱出不来

对于网络爬虫如何防止环路(死循环)与防止陷阱(出不来)? 那么咱们如何避免拜访曾经拜访过的页面呢?设置一个标记即可。整个互联网就是一个图构造,咱们通常应用DFS(深度优先搜寻)和BFS(广度优先搜寻)进行遍历。所以,像遍历一个简略的图一样,将拜访过的结点标记一下即可。 解决方案 1.限定爬虫的最大循环次数,对于某Web站点拜访超过肯定阈值就跳出,防止有限循环;2.保留一个已拜访Url列表,记录该页面是否被拜访过的; 对于抓取策略 1.调度爬虫抓取压力,限度拜访次数与工夫,超时后主动切换。 2.限度Url的字节长度,环路可能会使得Url长度减少。 3.去掉无用字符与URL别名,制订Url的标准。 快搜常识搜寻,未完待续!

July 12, 2020 · 1 min · jiezi

爬虫管理平台-Crawlab-专业版新功能介绍-结果数据集成

前言Crawlab 是一个基于 Golang 的分布式爬虫管理平台,旨在帮助爬虫工程师和开发人员轻松管理一切爬虫。Crawlab 创建之初,就利用 Shell 执行原理使其能够执行理论上任何编程语言开发的爬虫,以及管理任何爬虫框架。自 2019 年 3 月份发布第一个版本,Crawlab 迭代发展了一年多,成为了最受欢迎的爬虫管理平台。在产品不断变得完善的同时,开发组也意识到有必要为企业量身打造一个稳定性更高、更灵活、更实用的版本。 专业版Crawlab 专业版 (Crawlab Pro) 是针对专业用户以及企业量身打造的。它的稳定性更高,相较于 Crawlab 社区版功能更强大。专业版在底层特别是数据库层面做了大量的优化,保证爬虫任务能够稳定而高效的运行并抓取数据。此外,专业版相较于社区版有很多强大的专属功能,例如节点、数据库监控功能,SQL 数据库 (MySQL、Postgres) 集成等等。在未来,我们将支持更加高级的可配置爬虫。 结果数据源集成Crawlab 专业版首次发布的版本中(v0.1.0),我们支持了除 MongoDB 以外的其他数据源,包括 MySQL 和 Postgres 数据库。在最近的 Crawlab 专业版更新中(v0.1.1),我们加入 Kafka 和 ElasticSearch 的集成,增强了结果储存格式的多样化,满足了以 Kafka、ElasticSearch 作为主要结果储存形式的用户。 因此,Crawlab 专业版现在可以集成大部分数据源,包括 MongoDB、MySQL、Postgres、Kafka、ElasticSearch。当然,我们未来还将集成更多的数据源,例如 SQL Server、Cassandra、HBase 等等。 用户可以在 Crawlab 专业版上运行爬虫,将数据抓取下来,并通过调用 Crawlab SDK 来发送结果到指定的数据源中(如下图所示)。 调用 Crawlab SDK 的方式非常简单,下面是一个 Scrapy Pipeline 中发送结果数据的例子。只需要调用 save_item 这一个方法即可完成结果的保存。您不需要单独指定数据源的连接地址、端口、用户名、密码等等,Crawlab SDK 会帮您轻松完成结果数据集成。 from crawlab import save_itemclass ExamplePipeline(object): def process_item(self, item, spider): save_item(item) # 只需要调用这一句就可以了 return item集成成功后,您可以在 Crawlab 的 Web 界面上看到抓取到的结果数据(如下图)。 ...

July 5, 2020 · 1 min · jiezi

Scrapy框架初探

Scrapy基本介绍scrapy是一种用于爬虫的框架,并提供了相当成熟的模版,大大减少了程序员在编写爬虫时的劳动需要。 Command line tool & Project structure使用scrapy需要先创建scrapy project,之后再于project文件夹路径下生成spider(爬虫)文件,编写完程序后,再运行爬虫(手动指定保存文件)。以上过程由命令行执行,具体如下: scrapy startproject <myproject>scrapy genspider <spider_name> <domain>scrapy crawl <spider_name> [-o filename]后面两个命令均要在myproject文件夹(第一个myproject)路径下执行。而由第一个命令创建的scrapy项目结构如下: myproject/ scrapy.cfg myproject/ __init__.py items.py middlewares.py pipelines.py settings.py spiders/ __init__.py spider_name.py Scrapy Overview上图是scrapy的基本结构,易见scrapy的程序执行和数据流动是由engine来调度控制的,关于该结构的具体解释见:scrapy document overview. Scrapy Spider详解基础的使用scrapy一般只需要在spider_name.py中进行编写,该文件是我们使用scrapy genspider <spider_name>命令后自动创建中,文件中还自动import了scrapy并且还自动创建了一个模版spider类(自动继承自scrapy.Spider)。spider的功能简介于此:scrapy document spider。这里介绍一些常用的scray.Spider类的属性及方法:Attribute:name:name属性即是我们使用命令行时所指定的spider_name,这是scrapy框架中用于标识spider所用,每个spider都必须有一个独一无二的name。allowed_domains:该属性为一个以string为元素的list,每个元素string为一个domain,限定爬虫所能爬取的网址。start_urls:该属性同样为一个以string为元素的list,每个元素string对应一个完整的url,是spider开始时需要爬取的初始网址。custom_settings:该属性的形式为一个dict,即如修改user-agent,开启pipeline,指定log_level之类的,并且局限于http header。method:start_requests:该方法用于在scrapy开始爬虫时以start_urls返回一个Request iterable或者一个generator,该方法在一次爬虫过程中仅被调用一次。其默认实现为:Request(url, dont_filter=True) for each url in start_urls。parse:这是spider类中最重要的一个方法。由scrapy文档中对spider的介绍,spider每发出一个Request,便要对该Request的Response进行处理,处理的函数称为callback function,即 one Request corresponds to one Callback。而parse就是默认的callback函数,它负责对传回的response进行解析(xpath),提取数据(item dict-like object)并交予,并且还会根据需要发出新的Request请求。和start_requests一样,它的返回值也须要是一个iterable或是一个generator。一般来说,先yield item,再根据对response的解析得到新的url以yield Request(url,callback)。这里对于response的解析和数据提取交予过程略过,具体可以见于b站教程。 由以上介绍可见对于start_request和parse方法的返回值要求,scrapy框架的内部代码逻辑应该是像for循环一样轮询两者的返回值,对于parse方法还需要判断其返回值的类型(item/Request)来区别处理。 Scrapy Request and ResponseTypically, Request objects are generated in the spiders and pass across the system until they reach the Downloader, which executes the request and returns a Response object which travels back to the spider that issued the request. ...

July 4, 2020 · 2 min · jiezi

python-urllib-request拾遗

python urllib request 简介urllib.request : open and read URLs与许多python模块一样,urllib.request中对于我们的使用也提供了:1.面向过程2.面向对象这两种使用方式。 默认HTTP访问情况下面向过程urllib.request.urlopen(url,data=None)其中url为一个string或者Reuqest对象;data则是所谓的表单数据,在urllib.request模块中对于它的形式有一定规定,假如提供该参数http请求报文为post,不提供则为get。该函数返回一个file like object,有read、readline、readlines等常见方法。注意read等方法返回的对象需要进行decode,一般是utf-8 面向对象在urllib.request中有三大对象:Request Opener Handler Request是对url的一种抽象,而且可以附加上对于data和header的一些指定。定义为urllib.request.Request(url,data=None,headers={}).参数headers即是对应http报文的header部分。Opener是用于访问url的类,上文的urlopen函数就是基于模块中内置的一个Opener的。模块中只有urllib.request.OpenerDirector一个Opener类,而该类的具体定义如下: | Methods defined here:| | __init__(self)| Initialize self. | | add_handler(self, handler)| | close(self)| | error(self, proto, *args)|| open(self, fullurl, data=None, timeout=<object object at 0x00000221AFDDA260> )Handler则是用于处理访问中的一些“dirty”工作,如代理、cookies、身份认证等工作。模块中定义了很多Handler类,BaseHandler是基类,HTTPRedirectHandler处理重定向,爬虫中常用ProxyHandler,其定义为urllib.request.ProxyHandler(proxies=None),proxies参数是一个字典dict,形式为{"协议类型":"代理ip:端口号"}内在逻辑Request Opener Handler三类的关系可以用一句话概括:Opener open URLs(Request) via chained Handlers.Opener和Handler是怎么chain在一起的呢?上文OpenerDirector由一个add_handler的方法可以实现,或urllib.request.build_opener(handler) or urllib.request.install_opener(handler)可以把Handler作为参数传入,当然这两个函数返回的也都是OpenerDirector类实例。

July 1, 2020 · 1 min · jiezi

抖音用户搜索接口

我还是通过手机模拟器来进行抓包。下面这个是get请求搜索接口,返回的是相关搜索信息。只是一个简单的介绍。后面有搜索用户的POST接口,可以获取到用户id,以便尽一步操作。 搜索相关用户的接口: 点击使用浏览器访问。 所以可以根据刚的链接,来自定义我们需要搜索的关键词。例如: https://aweme-hl.snssdk.com/aweme/v1/search/sug/?keyword=朱一龙 搜索用户的POST接口:输入用户名,点击搜索。通过抓包可以看到有一条post请求,返回了用户列表的数据。携带的post参数有:cursor=0&keyword=%E9%9E%A0%E5%A9%A7%E7%A5%8E 等等。 但是这个post接口的params中有一些变动的,比如有一个时间戳等等。我们把数据格式化后来查看一下。 发现可以得到一个uid。而这个uid就是我之前一篇文章中,通过分享页面采集用户数据 所需要的id。链接:抖音用户信息采集案例 -2020/05/08更新:抖音APP接口分析:https://blog.csdn.net/weixin_43582101/article/details/105996989

June 22, 2020 · 1 min · jiezi

Java-多线程爬虫及分布式爬虫架构探索

这是 Java 爬虫系列博文的第五篇,在上一篇 Java 爬虫服务器被屏蔽,不要慌,咱们换一台服务器 中,我们简单的聊反爬虫策略和反反爬虫方法,主要针对的是 IP 被封及其对应办法。前面几篇文章我们把爬虫相关的基本知识都讲的差不多啦。这一篇我们来聊一聊爬虫架构相关的内容。 前面几章内容我们的爬虫程序都是单线程,在我们调试爬虫程序的时候,单线程爬虫没什么问题,但是当我们在线上环境使用单线程爬虫程序去采集网页时,单线程就暴露出了两个致命的问题: 采集效率特别慢,单线程之间都是串行的,下一个执行动作需要等上一个执行完才能执行对服务器的CUP等利用率不高,想想我们的服务器都是 8核16G,32G 的只跑一个线程会不会太浪费啦线上环境不可能像我们本地测试一样,不在乎采集效率,只要能正确提取结果就行。在这个时间就是金钱的年代,不可能给你时间去慢慢的采集,所以单线程爬虫程序是行不通的,我们需要将单线程改成多线程的模式,来提升采集效率和提高计算机利用率。 多线程的爬虫程序设计比单线程就要复杂很多,但是与其他业务在高并发下要保证数据安全又不同,多线程爬虫在数据安全上到要求不是那么的高,因为每个页面都可以被看作是一个独立体。要做好多线程爬虫就必须做好两点:第一点就是统一的待采集 URL 维护,第二点就是 URL 的去重, 下面我们简单的来聊一聊这两点。 维护待采集的 URL多线程爬虫程序就不能像单线程那样,每个线程独自维护这自己的待采集 URL,如果这样的话,那么每个线程采集的网页将是一样的,你这就不是多线程采集啦,你这是将一个页面采集的多次。基于这个原因我们就需要将待采集的 URL 统一维护,每个线程从统一 URL 维护处领取采集 URL ,完成采集任务,如果在页面上发现新的 URL 链接则添加到 统一 URL 维护的容器中。下面是几种适合用作统一 URL 维护的容器: JDK 的安全队列,例如 LinkedBlockingQueue高性能的 NoSQL,比如 Redis、MongodbMQ 消息中间件URL 的去重URL 的去重也是多线程采集的关键一步,因为如果不去重的话,那么我们将采集到大量重复的 URL,这样并没有提升我们的采集效率,比如一个分页的新闻列表,我们在采集第一页的时候可以得到 2、3、4、5 页的链接,在采集第二页的时候又会得到 1、3、4、5 页的链接,待采集的 URL 队列中将存在大量的列表页链接,这样就会重复采集甚至进入到一个死循环当中,所以就需要 URL 去重。URL 去重的方法就非常多啦,下面是几种常用的 URL 去重方式: 将 URL 保存到数据库进行去重,比如 redis、MongoDB将 URL 放到哈希表中去重,例如 hashset将 URL 经过 MD5 之后保存到哈希表中去重,相比于上面一种,能够节约空间使用 布隆过滤器(Bloom Filter)去重,这种方式能够节约大量的空间,就是不那么准确。关于多线程爬虫的两个核心知识点我们都知道啦,下面我画了一个简单的多线程爬虫架构图,如下图所示: ...

October 16, 2019 · 3 min · jiezi

Java-爬虫服务器被屏蔽不要慌咱们换一台服务器

这是 Java 爬虫系列博文的第四篇,在上一篇 Java 爬虫遇上数据异步加载,试试这两种办法!) 中,我们从内置浏览器内核和反向解析法两个角度简单的聊了聊关于处理数据异步加载问题。在这篇文章中,我们简单的来聊一聊爬虫时,资源网站根据用户访问行为屏蔽掉爬虫程序及其对应的解决办法。 屏蔽爬虫程序是资源网站的一种保护措施,最常用的反爬虫策略应该是基于用户的访问行为。比如限制每台服务器在一定的时间内只能访问 X 次,超过该次数就认为这是爬虫程序进行的访问,基于用户访问行为判断是否是爬虫程序也不止是根据访问次数,还会根据每次请求的User Agent 请求头、每次访问的间隔时间等。总的来说是由多个因数决定的,其中以访问次数为主。 反爬虫是每个资源网站自保的措施,旨在保护资源不被爬虫程序占用。例如我们前面使用到的豆瓣网,它会根据用户访问行为来屏蔽掉爬虫程序,每个 IP 在每分钟访问次数达到一定次数后,后面一段时间内的请求返回直接返回 403 错误,以为着你没有权限访问该页面。所以我们今天再次拿豆瓣网为例,我们用程序模拟出这个现象,下面是我编写的一个采集豆瓣电影的程序 /** * 采集豆瓣电影 */public class CrawlerMovie { public static void main(String[] args) { try { CrawlerMovie crawlerMovie = new CrawlerMovie(); // 豆瓣电影链接 List<String> movies = crawlerMovie.movieList(); //创建10个线程的线程池 ExecutorService exec = Executors.newFixedThreadPool(10); for (String url : movies) { //执行线程 exec.execute(new CrawlMovieThread(url)); } //线程关闭 exec.shutdown(); } catch (Exception e) { e.printStackTrace(); } } /** * 豆瓣电影列表链接 * 采用反向解析法 * * @return */ public List<String> movieList() throws Exception { // 获取100条电影链接 String url = "https://movie.douban.com/j/search_subjects?type=movie&tag=热门&sort=recommend&page_limit=200&page_start=0"; CloseableHttpClient client = HttpClients.createDefault(); List<String> movies = new ArrayList<>(100); try { HttpGet httpGet = new HttpGet(url); CloseableHttpResponse response = client.execute(httpGet); System.out.println("获取豆瓣电影列表,返回验证码:" + response.getStatusLine().getStatusCode()); if (response.getStatusLine().getStatusCode() == 200) { HttpEntity entity = response.getEntity(); String body = EntityUtils.toString(entity, "utf-8"); // 将请求结果格式化成json JSONObject jsonObject = JSON.parseObject(body); JSONArray data = jsonObject.getJSONArray("subjects"); for (int i = 0; i < data.size(); i++) { JSONObject movie = data.getJSONObject(i); movies.add(movie.getString("url")); } } response.close(); } catch (Exception e) { e.printStackTrace(); } finally { client.close(); } return movies; }}/** * 采集豆瓣电影线程 */class CrawlMovieThread extends Thread { // 待采集链接 String url; public CrawlMovieThread(String url) { this.url = url; } public void run() { try { Connection connection = Jsoup.connect(url) .method(Connection.Method.GET) .timeout(50000); Connection.Response Response = connection.execute(); System.out.println("采集豆瓣电影,返回状态码:" + Response.statusCode()); } catch (Exception e) { System.out.println("采集豆瓣电影,采集出异常:" + e.getMessage()); } }}这段程序的逻辑比较简单,先采集到豆瓣热门电影,这里使用直接访问 Ajax 获取豆瓣热门电影的链接,然后解析出电影的详情页链接,多线程访问详情页链接,因为只有在多线程的情况下才能达到豆瓣的访问要求。豆瓣热门电影页面如下: ...

October 15, 2019 · 5 min · jiezi

我是如何使用laravel写爬虫应用的

上面这个图是我设计的爬虫架构,这个架构逻辑比较简单。 首先是链接池,链接池存储需要爬取的网页链接,每个链接有当前爬取状态,尝试次数等信息,爬取状态分为:waiting(等待),going(正在进行),success(爬取成功),fail(爬取失败)。链接的默认状态是waiting,当爬虫正在爬取这个链接的内容的时候,链接进入going状态,链接内容爬取成功进入success状态,爬取失败进入fail状态。链接的另一个参数是尝试次数,当链接爬取失败则尝试次数加1并再次进入waiting状态,设定尝试次数阈值,比如设定阈值为3,当尝试次数超过3次,则进入fail状态。 爬虫管理者负责创建爬虫任务,我们可以创建一个task来定期运行爬虫管理者。爬虫管理者从爬虫池中选取一定数量的处于waiting状态的链接,创建爬虫任务。 爬虫任务接受一个目标链接,然后针对链接的格式运行对应的解析器。如果发现新的目标链接,则将新发现的链接放入链接池。这个地方需要注意的是爬虫在请求链接内容的时候,要使用代理,这样可以防止同一个ip频繁请求被封的情况。 刚开始链接池是空的,所以我们需要放入第一个目标链接,这样爬虫会不断的发现新链接,然后将新链接作为目标链接再次爬取内容,如果效果好的话,爬虫会一直运行知道没有新的链接发现未知。 下面这个是我最近做的一个微信小程序,用来快速查找澳洲保健品中文信息的小工具。

October 4, 2019 · 1 min · jiezi

学-Java-网络爬虫需要哪些基础知识

说起网络爬虫,大家想起的估计都是 Python ,诚然爬虫已经是 Python 的代名词之一,相比 Java 来说就要逊色不少。有不少人都不知道 Java 可以做网络爬虫,其实 Java 也能做网络爬虫而且还能做的非常好,在开源社区中有不少优秀的 Java 网络爬虫框架,例如 webmagic 。我的第一份正式工作就是使用 webmagic 编写数据采集程序,当时参与了一个舆情分析系统的开发,这里面涉及到了大量网站的新闻采集,我们就使用了 webmagic 进行采集程序的编写,由于当时不知道其设计原理,使用起来还是走了不少弯路,其实 webmagic 的设计借鉴了 Scrapy ,所以它也能像 Scrapy 一样强大,关于 webmagic 框架我们将在后续展开详细的讨论。 在后面的几年工作中,也参与了好几个爬虫项目,但是大多数都是使用 Python ,抛开语言不谈,爬虫也是有一套思想的。这些年写爬虫程序,对我个人的技术成长帮助非常大,因为在爬虫的过程中,会遇到各种各样的问题,其实做网络爬虫还是非常考验技术的,除了保证自己的采集程序可用之外,还会遇到被爬网站各种奇奇怪怪的问题,比如整个 HTML 页面有没一个 class 或者 id 属性,你要在这种页面提取表格数据,并且做到优雅的提取,这就是非常考验你的想象力以及技术啦。非常有幸在刚入行的时候就接触到了网络爬虫这一块,它加快了我对互联网的理解和认知,扩宽了我的视野。 这几年来网络爬虫比较火,如果你想学习 Java 网络爬虫,我根据我自己的经验总结了一下,想入门学习 Java 网络爬虫需要知道的四点基础知识。 1、有 “道德” 的爬虫我为什么会把这一点放在最前面呢?因为我觉得这一点比较重要,什么叫有 “道德” 的爬虫呢?就是遵循被爬服务器的规则,不去影响被爬服务器的正常运行,不把被爬服务搞垮,这就是有 “道德” 的爬虫。 经常有人讨论的一个问题就是爬虫合法吗?知乎一下你看到的将是这样的 答案千千万,在这众多答案中,我个人比较赞同下面的这个回答 爬虫作为一种计算机技术就决定了它的中立性,因此爬虫本身在法律上并不被禁止,但是利用爬虫技术获取数据这一行为是具有违法甚至是犯罪的风险的。所谓具体问题具体分析,正如水果刀本身在法律上并不被禁止使用,但是用来捅人,就不被法律所容忍了。爬虫为不违法?取决于你做的事情为不违法,网络爬虫的本质是什么?网络爬虫的本质是用机器代替人工去访问页面。我查看公开的新闻肯定不犯法,所以我去采集公开在互联网上的新闻也不犯法,就像各大搜索引擎网站一样,别的网站巴不得别搜索引擎的蜘蛛抓取到。另一种恰恰相反的情况是去采集别人隐私的数据,你自己去查看别人的隐私信息这就是一种违法的行为,所以用程序去采集也是违法的,这就像答案中所说的水果刀本身不违法,但是用来捅人就违法啦。 要做到有 “道德” 的爬虫,Robots 协议是你必须需要了解的,下面是Robots 协议的百度百科 在很多网站中会申明 Robots 协议告诉你哪些页面是可以抓取的,哪些页面是不能抓取的,当然 Robots 协议只是一种约定,就像公交车上的座位一样标明着老弱病残专座,你去坐了也不违法。 除了协议之外,我们的采集行为上也需要克制,在 『数据安全管理办法(征求意见稿)』的第二章第十六条指出: 网络运营者采取自动化手段访问收集网站数据,不得妨碍网站正常运行;此类行为严重影响网站运行,如自动化访问收集流量超过网站日均流量三分之一,网站要求停止自动化访问收集时,应当停止。这条规定指出了爬虫程序不得妨碍网站正常运行,如果你使用爬虫程序把网站搞垮了,真正的访问者就不能访问该网站了,这是一种非常不道德的行为。应该杜绝这种行为。 除了数据的采集,在数据的使用上同样需要注意,我们即使在得到授权的情况下采集了个人信息数据,也千万不要去出卖个人数据,这个是法律特别指出禁止的,参见: 根据《最高人民法院 最高人民检察院关于办理侵犯公民个人信息刑事案件适用法律若干问题的解释》第五条规定,对“情节严重”的解释: ...

October 4, 2019 · 2 min · jiezi

如何打造一个上千Star的Github项目

前言每一个程序员都或多或少接触过Github,至少是听说过吧。而Github最大的好处是在于程序员可以不用付出任何费用,可以在上面参考、借鉴甚至是照搬其他人贡献的项目,因为这一切都是开源的。另外,任何一个Github用户也可以在上面对自己感兴趣的项目做出贡献。所谓贡献,就是对已有的代码进行更正、优化、开发等操作,让项目发展得越来越棒。而且,Github的Star机制让项目的受欢迎程度得到很好的展示。如果您去Github上搜索一个Vue的后台管理模板,您很可能会选择几万Star的Vue-Element-Admin(就像您去挑选晚餐地点,一般会去大众点评上选评价足够高的餐厅)。千万别小看Star这样类似点赞的机制,这些Star的背后是大多数程序员的认可,Github Star数量可比朋友圈的点赞数量要有技术含量得多。我不是在吹捧那些点赞数多的项目,我只是在强调一种增加技术影响力的方式:打造一个上千Star的Github项目。Github作为世界上最大的开源项目管理中心,可以让任何人有机会实现这个目标。 另外,作者只是一个普通人,是一点一滴尝试了很多办法、做出了很多努力才收获了上千Star,并没有很多大佬那样更出色的项目。作者将从自己的角度给大家介绍自己积累的经验教训,从项目定位、产品开发、推广渠道等方面来讲。如果大佬们有更多更优秀的作品和经验,欢迎随时交流。 关于作者的项目Crawlab是作者近期在开发的Github项目,是一个专注于爬虫的管理平台。这个项目从今年3月份上线以来,到现在总共收到了大约1.1k个Github Stars(如下图)。 最初Crawlab是基于Celery的分布式爬虫管理平台,后来由于稳定性、性能等考虑,在v0.3.0版本中将Python Celery改为了Golang,稳定性和性能都得到了极大的提升。Crawlab不仅支持主流的爬虫框架,例如Scrapy,还支持Python、NodeJS、Go、Java、PHP等多种编程语言以及多种爬虫框架。Crawlab的目的是让爬虫工程师以最轻松的方式来管理和运行他们开发的爬虫。Crawlab自上线以来,一直受到爬虫爱好者们和开发者们的好评,不少使用者还表示会用 Crawlab 搭建公司的爬虫平台。在文末有关于Crawlab的往期文章,想要了解更多Crawlab的读者可以去看一下。 Github: https://github.com/tikazyq/crawlab 寻找痛点任何一个产品,都是为需要的人服务的。这个受众可以是您自己,也可以是您的朋友,也可以是完全不认识的陌生人。此外,一个优秀的产品必须解决用户的问题,或者说是痛点。就像雪中送炭那样,恰当的时间出现了恰当的解决办法,别人才会毫不犹豫的使用这个东西。分布式爬虫工具PySpider(13.5k stars)为什么那么受欢迎,就是因为它解决了那些需要抓取大量网站的爬虫开发工程师的管理困难的痛点。Web框架Flask(46k stars)因为其解决了Web开发上手困难的问题(相比于Django、Tornado这样的传统框架),受到了Web开发者的喜爱,在Github上占据一席之地。基于Vue Element的管理后台框架 Vue-Element-Admin(39k stars)让前端开发不用从零开始搭建管理后台,开箱即用,成为Vue开发者的香饽饽。如何寻找到用户的痛点是我们能打造一个优秀产品或项目的首要问题。 从自身周围寻找用户痛点您可能会纳闷,我周围没有感觉到有痛点的用户。如果您这么觉得,您一定是没有注意观察。在工作中我们会处理成千上万的问题,包括一些技术问题或非技术问题。而这些问题的产生,一定是有没有满足到的需求导致的,而这正是可能的出发点。 例如,作者的开源爬虫管理平台Crawlab就是在思考一个工作问题时诞生的。作者所在的部门有上百个爬虫,其中包括了Selenium爬虫和其他类型的爬虫。我们当时的爬虫管理方式和实现方式都有非常多的局限性,导致了扩展性不高、排查异常困难等问题。我们有一个Web UI界面,但是仅仅局限于业务,没有专注于爬虫本身。作者当时思考,是不是只有我们公司才遇到这个问题,还是这个问题是一个普遍问题,几乎每个需要爬虫的公司都会遇到。为了验证这个假设,作者花了半个月的时间做了一个最小可行性产品(MVP),Crawlab v0.1版本,只有最基本的执行爬虫脚本的功能。结果,第一版发布后陆陆续续收到正面反馈,也有不少改进建议。第一天的Star数就达到了30,后面两天涨到了100。这验证了我的假设,爬虫管理困难这个问题是普遍存在的,大家都觉得Crawlab是个不错的idea,愿意来尝试。这才开始让作者更有动力不断完善这个产品。所以,从身边的问题出发是个很好的开始。 扩大自己的知识圈知识就是力量,这句话很正确。在开发产品的过程中我们不仅需要了解自身产品,还需要了解自己的客户,更需要了解自己产品的市场及竞争对手。我们需要不断完善更新自己的知识库,以在后续的开发、推广产品过程中作出最优的选择。扩大知识圈的渠道有很多,掘金就是一个很不错的渠道,上面不仅有各种优质的技术文章,更有掘金小册,可以帮助读者快速了解一项技术。除了掘金以外,还有其他很多优秀的技术网站,包括SegmentFault、GitChat、V2ex等等。相信各位读者及大佬也有自己获取知识的渠道,可以加作者微信tikazyq1分享交流下。 以自己举例,在开发Crawlab之前,我已经是掘金的重度阅读者,而且还购买了韦世东写的掘金小册《Python 实战:用 Scrapyd 打造个人化的爬虫部署管理控制台》,在小册中不仅了解了爬虫部署管理平台的原理和基础知识,还了解到了市场上已存在的爬虫管理平台Gerapy。这为后来我开发Crawlab积累了大量的宝贵经验。我又了解了市面上所有的爬虫平台,无一例外是基于Scrapyd的,因此我在想万一有想要管理非scrapy爬虫的呢,他们会使用什么工具来管理。后面我又做了些研究,发现Crawlab才是他们解决该问题的方法。因此,花一些钱购买知识是非常值得的,我在获取知识的同时帮助了我寻找痛点。 完善产品您可能会认为本小节是在讲如何开发产品,但不是。本小节将讲如何在开发产品中花最少的时间干最正确的事情。用户满意了,增加Star数是自然而然的事情。 我们不可能在第一时间做到一步到位。因此,不断完善产品来满足用户需求变得尤为重要。《精益创业》中的作者定义了最小可行性产品(Minimal Viable Product,MVP),也就是以最低成本尽可能展现核心概念的产品策略。因为我们的时间很宝贵(对于经常需要加班的朋友来说,更是如此),花费大量时间在完善各种不必要的功能上会是极为浪费的行为。我们没有必要花时间开发那些用户认为不重要的功能。如何做到不浪费时间,只开发用户喜爱的功能呢? 询问用户直接询问用户是最为直接也是最有效的方式。我在发布完Crawlab第一版的时候成立了一个微信群,并在推广的时候邀请用户加入,因此积累了一部分实际用户与潜在用户。我在开发一个新功能前我会询问群里的朋友,他们是否喜爱这样一个功能,如果开发这样的功能他们是否会使用。我询问群友是否都在用Crawlab,很多人都回答感兴趣但是没有。这显然是个令人失望的答案,但我并没有丧失信心。我接着问了为什么,结果他们都认为Crawlab虽然不错,但部署太麻烦了。然后我在群里问了他们关于Docker的意见,群友一致认为如果有Docker会非常棒。而现在,Docker变成了部署Crawlab的首选项,几乎每一个用户都是用的Docker来部署Crawlab。 另外一个有效的询问用户的方式是问卷调查。当Crawlab的Golang版本v0.3.0发布后,我在群里发放了问卷调查,邀请用户回答他们认为最重要的功能,答案如下图。统计后发现,任务监控和可配置爬虫是最为重要的两个功能。因此,我会在接下来的开发过程中优先考虑开发这两个功能。 统计用户行为现在的大公司都会在网站或App中做埋点上报统计用户行为数据。这是也是非常有效的了解用户偏好的工具,而且这种方法还排除了用户偏见(问卷中被调查者可能心口不一)。在Crawlab中,嵌入了一小段JS代码,让用户行为数据得到统计,上传到第三方统计工具百度统计上面。下图是用户用得最多的行为。 从该报表可以看到,用户主要在爬虫页面和任务页面来回切换标签,我可能还不知道它是哪些标签,但我肯定会在这两个页面上下更多功夫,来优化Crawlab。当然,这一切的用户数据收集都是经过用户同意的,用户如果在首次加载选择不同意的话,数据将不会上传(有些框架都不会询问用户的意愿,它们会直接上传统计数据)。 其他完善产品的工具例如A/B测试作者不会讲了,因为这要求更多的资源和流量,Crawlab还没有这样雄厚的资源。感兴趣的可以自己去百度。 产品推广如果没有人了解您的产品,您的产品再优秀也只能被凉在一边,无人问津,沾染灰尘。因此,推广您的产品也是增加Github Star数的非常重要的途径。下面我将介绍一些常用的产品推广的方式,包括一些常用的有效渠道,以及如何查看后台数据来监控渠道有效性。 写技术文章这是我认为最有效的渠道了。在推广Crawlab的实践中,我发现在掘金上写文章是一个非常重要的推广产品的方式。下图是用户通过各种渠道(微信群、朋友圈、掘金自然流量)接触到技术文章方式。 将您的文章展示给更多用户有助于提高您产品的曝光度,而且如果用户对您产品感到满意的话,会点击您嵌入到文章中的导航到Github项目地址的链接,然后给您项目Star。我每次在掘金上写完文章之后,我都会将文章的链接发到各个相关的微信群里,感兴趣的群友会点击链接到文章里,从而更有可能到Github上给项目点Star。同理,我也会发布到朋友圈或其他渠道。如果文章读者不会第一时间Star,没关系,他在了解到您项目之后很可能会在后面的曝光过程给您Star。在市场营销中,这就叫再营销(Remarketing)。千万不要觉得写文章是一件没有用的事情,相反,它将帮助您更早地收获上千Star项目。 当然,我们不仅仅有掘金这一个渠道,我们还有很多其他选择。下面列出了我能够知道的推广渠道。 渠道简介掘金偏前端的技术社区V2ex技术论坛GitChat技术知识分享平台微信公众号不用过多介绍了SegmentFault技术问答、技术博客网站知乎知识问答网站CSDNIT技术社区开源中国开源技术交流社区简书博客网站其他读者可以微信我tikazyq1添加您应该定期去Github上查看您的流量来源,看看哪些渠道是有用的。具体地址是: 主页 -> Insights -> Traffic。Crawlab最近30天的Traffic如下图。 可以看到,Github内部、掘金以及V2ex是Crawlab的三大流量来源。上图中8月14号的流量高峰是因为我在当天发了一篇文章《如何快速搭建实用的爬虫管理平台》。 当然,要发布这么多文章这么多平台不是一件容易的事情。作者和另外一些朋友打算基于爬虫的方式,写一个自动发布文章的工具,名字叫ArtiPub(意思为Article Publisher),Github为 https://github.com/crawlab-te... 。现在框架搭建好了,准备不断开发。欢迎大家随时关注。 改造Github首页光是有流量还不行,您还需要对让自己的README显得更加具有吸引力。下列是几个增加吸引力的内容,建议您都该考虑加入到您的项目主页中。 徽章中英文目录产品截图使用安装社区以上几种方式都可以让您的Github首页显得更加专业。您需要清晰地阐述您的项目的安装方式,以及中文介绍,这将有利于让您的读者更加容易上手您的产品。产品截图是非常有效的展示您产品的方式,人都是视觉动物,他们会更倾向于有截图的产品(Airbnb就做的很不错,他们在房屋截图中用了大光圈照片)。如果您能够加上Logo之类的就更棒了。 下图是Crawlab的首页,虽然不完美,但对于增加吸引力来说很有效。 项目管理项目管理是一个可选项,但对于推进和监控项目进度来说是非常有效的。可以用酷炫的项目管理工具,例如Teambition、禅道或Trello,也可以用简单的Excel。我在Crawlab中就是用的Excel。下图是早期Crawlab项目管理的电子表格。 如何进行项目管理,这里我就不详细讲了,因为我也不是专业项目管理经理,也没有PMP证书。后期如果有需求的话我可以在另一篇文章中专门介绍项目管理。 总结本篇文章主要从自己项目Crawlab的角度出发,介绍了如何打造一个上千Star的Github项目的方法。其中包括寻找痛点、完善产品、产品推广和项目管理。相信每一个获得了上千Star的项目的作者都有自己的方法论。我看了老乡大佬CrossoverJie的相关文章《1K star+ 的项目是如何炼成的?》,获得了很多灵感,其中很多理念与本篇文章是相似的。不管如何,都祝愿读者能从本篇文章中收获知识,祝您能早日收获自己的上千Star项目。已经收获的,也可以分享下您的经验。 ...

August 17, 2019 · 1 min · jiezi

爬虫攻防实践

之前在学校曾经用过request+xpath的方法做过一些爬虫脚本来玩,从ios正式转前端之后,出于兴趣,我对爬虫和反爬虫又做了一些了解,并且做了一些爬虫攻防的实践。我们在爬取网站的时候,都会遵守 robots 协议,在爬取数据的过程中,尽量不对服务器造成压力。但并不是所有人都这样,网络上仍然会有大量的恶意爬虫。对于网络维护者来说,爬虫的肆意横行不仅给服务器造成极大的压力,还意味着自己的网站资料泄露,甚至是自己刻意隐藏在网站的隐私的内容也会泄露,这也就是反爬虫技术存在的意义。下面开始我的攻防实践。 开始先从最基本的requests开始。requests是一常用的http请求库,它使用python语言编写,可以方便地发送http请求,以及方便地处理响应结果。这是一段抓取豆瓣电影内容的代码。 import requestsfrom lxml import etreeurl = 'https://movie.douban.com/subject/1292052/'data = requests.get(url).texts=etree.HTML(data)film=s.xpath('//*[@id="content"]/h1/span[1]/text()')print(film)代码的运行结果,会输出 ['肖申克的救赎 The Shawshank Redemption']这就是最简单的完整的爬虫操作,通过代码发送网络请求,然后解析返回内容,分析页面元素,得到自己需要的东西。这样的爬虫防起来也很容易。使用抓包工具看一下刚才发送的请求,再对比一下浏览器发送的正常请求。可以看到,两者的请求头差别非常大,尤其requests请求头中的user-agent,赫然写着python-requests。这就等于是告诉服务端,这条请求不是真人发的。服务端只需要对请求头进行一下判断,就可以防御这一种的爬虫。当然requests也不是这么没用的,它也支持伪造请求头。以user-agent为例,对刚才的代码进行修改,就可以很容易地在请求头中加入你想要加的字段,伪装成真实的请求,干扰服务端的判断。 import requestsfrom lxml import etreeuser_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)'headers = { 'User-Agent' : user_agent }url = 'https://movie.douban.com/subject/1292052/'data = requests.get(url,headers=headers).texts=etree.HTML(data)film=s.xpath('//*[@id="content"]/h1/span[1]/text()')print(film)提高现阶段,就网络请求的内容上来说,爬虫脚本已经和真人一样了,那么服务器就要从别的角度来进行防御。有两个思路,第一个,分析爬虫脚本的行为模式来进行识别和防御。爬虫脚本通常会很频繁的进行网络请求,比如要爬取豆瓣排行榜top100的电影,就会连续发送100个网络请求。针对这种行为模式,服务端就可以对访问的 IP 进行统计,如果单个 IP 短时间内访问超过设定的阈值,就给予封锁。这确实可以防御一批爬虫,但是也容易误伤正常用户,并且爬虫脚本也可以绕过去。这时候的爬虫脚本要做的就是ip代理,每隔几次请求就切换一下ip,防止请求次数超过服务端设的阈值。设置代理的代码也非常简单。 import requestsproxies = { "http" : "http://111.155.124.78:8123" # 代理ip}user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)'headers = { 'User-Agent' : user_agent }url = 'https://movie.douban.com/subject/1292052/'res = requests.get(url = http_url, headers = headers, proxies = proxies)第二个思路,通过做一些只有真人能做的操作来识别爬虫脚本。最典型的就是以12306为代表的验证码操作。增加验证码是一个既古老又相当有效果的方法,能够让很多爬虫望风而逃。当然这也不是万无一失的。经过多年的发展,用计算机视觉进行一些图像识别已经不是什么新鲜事,训练神经网络的门槛也越来越低,并且有许多开源的计算机视觉库可以免费使用。例如可以在python中引入的tesseract,只要一行命令就能进行验证码的识别。 ...

July 16, 2019 · 1 min · jiezi

golang-使用chromedp获取页面请求日志network

package mainimport ( "context" "io/ioutil" "log" "os" "strings" "time" "github.com/chromedp/cdproto/network" "github.com/chromedp/chromedp")func main() { dir, err := ioutil.TempDir("", "chromedp-example") if err != nil { panic(err) } defer os.RemoveAll(dir) opts := append(chromedp.DefaultExecAllocatorOptions[:], chromedp.DisableGPU, chromedp.NoDefaultBrowserCheck, chromedp.Flag("headless", false), chromedp.Flag("ignore-certificate-errors", true), chromedp.Flag("window-size", "50,400"), chromedp.UserDataDir(dir), ) allocCtx, cancel := chromedp.NewExecAllocator(context.Background(), opts...) defer cancel() // also set up a custom logger taskCtx, cancel := chromedp.NewContext(allocCtx, chromedp.WithLogf(log.Printf)) defer cancel() // create a timeout taskCtx, cancel = context.WithTimeout(taskCtx, 10*time.Second) defer cancel() // ensure that the browser process is started if err := chromedp.Run(taskCtx); err != nil { panic(err) } // listen network event listenForNetworkEvent(taskCtx) chromedp.Run(taskCtx, network.Enable(), chromedp.Navigate(`https://www.iqiyi.com/v_19rsbimvyo.html`), chromedp.WaitVisible(`body`, chromedp.BySearch), )}//监听func listenForNetworkEvent(ctx context.Context) { chromedp.ListenTarget(ctx, func(ev interface{}) { switch ev := ev.(type) { case *network.EventResponseReceived: resp := ev.Response if len(resp.Headers) != 0 { // log.Printf("received headers: %s", resp.Headers) if strings.Index(resp.URL, ".ts") != -1 { log.Printf("received headers: %s", resp.URL) } } } // other needed network Event })}

July 9, 2019 · 1 min · jiezi

pythonrequests爬取知乎个人信息数据

效果预览地址:http://23.105.208.123/ 技术栈python3requestsredisechart源码地址https://github.com/MasakiOvO/... python需要安装的库requests,BeautifulSoup,redis,django 思路两个程序。 一个程序负责爬取用户关注和粉丝列表, 并把用户名存入set另一个程序负责根据用户名获取详细信息,存入hash维护 两个列表 1.已爬用户 2.未爬用户第一个程序的流程: 当未爬set不为空时:执行如下操作:每次从未爬取列表中取出一个用户名 根据用户名,获取他的关注与粉丝并遍历,如果用户既没在已爬用户,也没在未爬用户 加入未爬用户列表。第二个程序的流程 每次从未爬set中取出一个用户名,调用接口获取详细信息并存入redis hash中接口获取打开某个人的个人主页,按F12 选择XHR 然后点击图1的关注我的人,和我关注的人,在图2就可以看到header和cookie。 获取用户列表Api: https://www.zhihu.com/api/v4/...{username}/{type}?include=data[*].answer_count,articles_count,gender,follower_count,is_followed,is_following,badge[?(type=best_answerer)].topics&offset=20&limit=20username 是用户名, type 是类型, 有两种可选 [followers,followees], followers是粉丝 followees是关注改变offset的值可以获取分页 获取个人信息Api: https://www.zhihu.com/people/...user_token是用户名这个api中 返回的是html页面,在html页面中有一个scirpt标签里面的内容是json格式的用户信息,这就很方便了,直接获取键值对 对应的属性就ok。 发送请求F12查看接口,把header和cookie放进requests.get()方法中。 剩下的就很简单了,不赘述了。 总结还有很多优化的地方。比如加入中间件,来提升爬虫的稳定性。对数据进行分类,统计城市分布。

June 27, 2019 · 1 min · jiezi

Cendertron动态爬虫与敏感信息泄露检测

Cendertron,动态爬虫与敏感信息泄露检测Cendertron = Crawler + RendertronCendertron https://url.wx-coder.cn/HinPM 是基于 Puppeteer 的 Web 2.0 动态爬虫与敏感信息泄露检测工具。其依托于 xe-crawler 的通用爬虫、调度与缓存模型,新增了 Monkey Test 以及 Request Intercept 等特性,以期尽可能多地挖掘页面与请求。同时针对渗透测试的场景,Cendertron 内置了目录扫描、敏感文件扫描的能力,能够模拟用户实际在浏览器登录状态下的自定义字典爆破。Cendertron 在大量实践的基础上设置了自身的去重策略,能够尽可能地避免重复爬取,加快扫描速度。Cendertron 同时也是正在闭源开发的 Chaos-Scanner 模块化安全扫描解决方案的一部分,为基础扫描与智能扫描提供前置输入。 Usage | 使用Locally Development | 本地开发在本地开发中,我们只需要如正常的 Node 项目一样启动,其会使用 Puppeteer 内置的 Headless Chrome 来执行界面渲染操作: $ git clone https://github.com/wx-chevalier/Chaos-Scanner$ cd cendertron$ yarn install$ npm run dev启动之后可以按提示打开浏览器界面: 这里我们可以以 DVWA 作为测试目标,在输入框内输入 http://localhost:8082/ 然后执行爬取,即可得到如下结果: { "isFinished": true, "metrics": { "executionDuration": 116177, "spiderCount": 51, "depth": 4 }, "spiderMap": { "http://localhost:8082/vulnerabilities/csrf/": [ { "url": "http://localhost:8082/vulnerabilities/view_source.php?id=csrf&security=low", "parsedUrl": { "host": "localhost:8082", "pathname": "/vulnerabilities/view_source.php", "query": { "id": "csrf", "security": "low" } }, "hash": "localhost:8082#/vulnerabilities/view_source.php#idsecurity", "resourceType": "document" } // ... ] }}需要说明的是,因为 DVWA 是需要登录后爬取,因此如果想进行完整的测试请参考下文的 POST 方式创建任务。 ...

June 27, 2019 · 4 min · jiezi

三步教你用Node做一个微信脱单神器小白可上手

前言不知道大家最近有没有被python版的《微信每日说》刷屏呢,他可是霸占了github的python热门快两周了。我们前端的小伙伴是不是也看着有点眼馋呢,因为毕竟是不那么熟悉的python语言,学起来和用起来肯定没有那么舒服。想想要是用我们熟悉的js语言做一个属于自己的哄女友神器是不是很开心呢!???? 哄女友我们前端开发人员也是很认真的,自动哄女友(基友)神器我们也可以做! 项目介绍其实《微信每日说》小项目早在三月份都已经做好了,当时也发布了一篇文章《用Node+wechaty写一个爬虫脚本每天定时给女(男)朋友发微信暖心话》,有看过的朋友应该有印象的。由于上次分享的文章比较偏向于技术,被朋友说可能不太适合小白用户使用,在他的建议下呢,我又重新整理了一下现有代码,和制作了教学视频,方便任意人群的使用。 同时呢我也维护了两套项目,一个是本项目《微信每日说》适合入门人群,操作简单,配置方便,上手容易。另一个项目是《微信个人秘书》,功能较多,涵盖自动加群、自动加好友、自动回复、可设置定时提醒等功能。由于涉及到数据库的操作,所以主要面向有编程经验的群体,有兴趣的小伙伴可以参考《使用koa2+wechaty打造个人微信小秘书》。 本文介绍项目是用node和wechaty微信网页接口开发的一款小工具,可以定时给女朋友发每天的天气情况,天气提醒,每日一句。通过配置机器人api后还可以实现微信机器人自动陪女朋友聊天。 项目地址github: https://github.com/gengchen528/wechatBot 看看前端的小伙伴能不能把这个项目送上热门呢 哈哈 效果预览在三步走教学之前,先放上效果看一下 可以看到在指定的时间就会收到发送的消息,包括天气信息,天气提醒,还有你们在一起多少天了。当开启机器人后,女朋友就可以和小助手对话了,不过目前开源机器人的api都不是非常的智能,匹配的语义可能不是那么准确。所以有时候女朋友生气了,千万记得不要开启机器人,不然回答的不对是会被女朋友暴打的????! 此项目前期使用的是图灵机器人,但是最近图灵机器人做了限制,没有认证的用户不允许调取API,认证的用户每天也只有100条,这就很鸡肋了,女朋友还没哄过瘾呢就被限制了,这是很可怕的(手动滑稽)!所以现在更换了一个天行机器人的api接口,这个接口没有太多限制,送的调用次数也足够用,在项目中已经开放给大家用了,不过还是建议大家自己申请一个账号比较好,因为这个机器人可以自定义名称之类的,也可以设置自己需要的回复内容。 视频教程移步:《三步教你用Node做一个微信哄女友神器》 三步走教程一、安装nodenode官网:https://nodejs.org/zh-cn/ 选择系统对应版本node下载安装,win建议.msi包安装,只需一直下一步即可,其他系统同理; windows下安装node步骤详细参考 https://www.cnblogs.com/liuqiyun/p/8133904.htmlMac下安装node详细步骤参考 https://blog.csdn.net/qq_32407233/article/details/83758899 Linux下安装node详细步骤参考 https://www.cnblogs.com/liuqi/p/6483317.html 安装完成后,按住键盘的shift+鼠标右键,选择在此处打开命令窗口。在命令行执行node -v出现版本号说明安装成功 二、下载代码并配置代码地址:https://github.com/gengchen528/wechatBot 访问此地址,直接下载zip包到本地桌面,然后解压; 进到目录中,找到config目录下的index.js文件 选中index.js文件,右击选择打开方式,没有安装代码编辑器的可以用记事本打开。有代码编辑器的直接用代码编辑器打开,建议非开发人员可以下载一个notepad++,下载链接链接:https://pan.baidu.com/s/1mWdEOaTQ1D6kihQveN1JHw 密码:fn9g,开发人员就各自发挥吧,相信每个人都有自己用的比较舒服的编辑器我就不推荐了](https://upload-images.jianshu... 配置文件中需要修改的地方,女朋友的微信备注姓名NAME必须要换一下,不然你发给我就不好了????,微信昵称NICKNAME最好也写一下,你和女朋友的纪念日MEMORIAL_DAY就不用说了,也要改一下。如果要发送天气信息,女朋友所在城市CITY肯定也是必须修改的,地区LOCATION不知道怎么拼写的话,我建议可以查一下墨迹的官网https://tianqi.moji.com/weather/china/在墨迹天气找到对应地区的天气后,查看一下网页地址栏,绿色标记的拼音填入CITY,红色标记的拼音填入LOCATION 每天发送的时间SENDDATE,这里的规则可以参见schedule目录下的index.js文件。这里0 06 8 * * *代表的是每天的早上8点06分0秒,我们通常只需配置前三个就可以了。如果需要开启机器人聊天的话,需要把AUTOREPLY设置为true,这里我放弃了图灵机器人,原因上面也说了,改用了天行机器人,但是不要抱太大希望,它并不是那么智能????。目前由于我自己账号的api次数还比较多,就在项目代码中开放给大家使用了,这里就不放出来,下载代码后只要修改一下AUTOREPLY就可以自动回复了。// 配置文件module.exports = { // 基础定时发送功能配置项(必填项) NAME: 'Leo_chen', //女朋友备注姓名 NICKNAME: 'Leo_chen', //女朋友昵称 MEMORIAL_DAY: '2015/04/18', //你和女朋友的纪念日 CITY: 'shanghai', //女朋友所在城市 LOCATION: "pudong-new-district", //女朋友所在区(可以访问墨迹天气网站后,查询区的英文拼写) SENDDATE: '0 06 8 * * *', //定时发送时间 每天8点0分0秒发送,规则见 /schedule/index.js ONE: 'http://wufazhuce.com/', ////ONE的web版网站 MOJI_HOST: 'https://tianqi.moji.com/weather/china/', //中国墨迹天气url //高级功能配置项(非必填项) AUTOREPLY: true, //自动聊天功能 默认关闭 AIBOTAPI: 'http://api.tianapi.com/txapi/robot/', //天行机器人API 注册地址https://www.tianapi.com/signup.html?source=474284281 APIKEY: '天行机器人apikey', //天行机器人apikey}三、开始运行程序配置完成好文件别忘记保存了,保存好就回到项目的主目录吧。这时候win系统的话就按住键盘的shift+鼠标右键,选择在此处打开命令窗口。 ...

June 24, 2019 · 1 min · jiezi

如何避免Puppeteer被前端JS检测

这两天开始看puppeteer,发现居然也能被前端js检测出来!?github的issue区找了找,原来puppeteer启动的chrome里面,是有navigator.webdriver属性的,搞什么搞么,老外真是做那啥还要立牌坊Orzissue里也看到了解决方案: await this.page.evaluateOnNewDocument(() => { Object.defineProperty(navigator, 'webdriver', { get: () => undefined, }); }但是说实话这个还是有点问题的,因为用"webdriver" in navigator还是能检测出来。想找找到底哪个环节把"webdriver"属性加上的,但是文本搜索发现puppeteer源码中并没有……后来发现是启动chrome的默认参数列表中有"--enable-automation"……找了一下这个命令行参数的说明: --enable-automation: Inform users that their browser is being controlled by an automated test.妈蛋纯粹是立牌坊用的,其它毛用没有……确认了就可以干掉它了,启动chrome时加个忽略默认参数即可: const browser = await puppeteer.launch({ignoreDefaultArgs: ["--enable-automation"]});

June 20, 2019 · 1 min · jiezi

3web爬虫scrapy模块介绍与使用

【百度云搜索,搜各种资料:http://bdy.lqkweb.com】【搜网盘,搜各种资料:http://www.swpan.cn】Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架。 其可以应用在数据挖掘,信息处理或存储历史数据等一系列的程序中。其最初是为了页面抓取 (更确切来说, 网络抓取 )所设计的, 也可以应用在获取API所返回的数据(例如 Amazon Associates Web Services ) 或者通用的网络爬虫。Scrapy用途广泛,可以用于数据挖掘、监测和自动化测试。 Scrapy 使用了 Twisted异步网络库来处理网络通讯。整体架构大致如下 Scrapy主要包括了以下组件: 引擎(Scrapy)用来处理整个系统的数据流处理, 触发事务(框架核心)调度器(Scheduler)用来接受引擎发过来的请求, 压入队列中, 并在引擎再次请求的时候返回. 可以想像成一个URL(抓取网页的网址或者说是链接)的优先队列, 由它来决定下一个要抓取的网址是什么, 同时去除重复的网址下载器(Downloader)用于下载网页内容, 并将网页内容返回给蜘蛛(Scrapy下载器是建立在twisted这个高效的异步模型上的)爬虫(Spiders)爬虫是主要干活的, 用于从特定的网页中提取自己需要的信息, 即所谓的实体(Item)。用户也可以从中提取出链接,让Scrapy继续抓取下一个页面项目管道(Pipeline)负责处理爬虫从网页中抽取的实体,主要的功能是持久化实体、验证实体的有效性、清除不需要的信息。当页面被爬虫解析后,将被发送到项目管道,并经过几个特定的次序处理数据。下载器中间件(Downloader Middlewares)位于Scrapy引擎和下载器之间的框架,主要是处理Scrapy引擎与下载器之间的请求及响应。爬虫中间件(Spider Middlewares)介于Scrapy引擎和爬虫之间的框架,主要工作是处理蜘蛛的响应输入和请求输出。调度中间件(Scheduler Middewares)介于Scrapy引擎和调度之间的中间件,从Scrapy引擎发送到调度的请求和响应。Scrapy运行流程大概如下: 引擎从调度器中取出一个链接(URL)用于接下来的抓取引擎把URL封装成一个请求(Request)传给下载器下载器把资源下载下来,并封装成应答包(Response)爬虫解析Response解析出实体(Item),则交给实体管道进行进一步的处理解析出的是链接(URL),则把URL交给调度器等待抓取创建Scrapy框架项目 Scrapy框架项目是有python安装目录里的Scripts文件夹里scrapy.exe文件创建的,所以python安装目录下的Scripts文件夹要配置到系统环境变量里,才能运行命令生成项目 创建项目 首先运行cmd终端,然后cd 进入要创建项目的目录,如:cd H:py14 进入要创建项目的目录后执行命令 scrapy startproject 项目名称 scrapy startproject pach1项目创建成功 项目说明 目录结构如下: ├── firstCrawler │   ├── __init__.py │   ├── items.py │   ├── middlewares.py │   ├── pipelines.py │   ├── settings.py │   └── spiders │       └── __init__.py ...

June 12, 2019 · 1 min · jiezi

1web爬虫requests请求

【百度云搜索,搜各种资料:http://bdy.lqkweb.com】【搜网盘,搜各种资料:http://www.swpan.cn】requests请求,就是用python的requests模块模拟浏览器请求,返回html源码 模拟浏览器请求有两种,一种是不需要用户登录或者验证的请求,一种是需要用户登录或者验证的请求 一、不需要用户登录或者验证的请求 这种比较简单,直接利用requests模块发一个请求即可拿到html源码 #!/usr/bin/env python# -*- coding:utf8 -*-import requests     #导入模拟浏览器请求模块http =requests.get(url="http://www.iqiyi.com/")     #发送http请求http.encoding = "utf-8"                             #http请求编码neir = http.text                                    #获取http字符串代码print(neir)得到html源码 <!DOCTYPE html><html><head><title>抽屉新热榜-聚合每日热门、搞笑、有趣资讯</title>        <meta charset="utf-8" />        <meta name="keywords" content="抽屉新热榜,资讯,段子,图片,公众场合不宜,科技,新闻,节操,搞笑" />        <meta name="description" content="            抽屉新热榜,汇聚每日搞笑段子、热门图片、有趣新闻。它将微博、门户、社区、bbs、社交网站等海量内容聚合在一起,通过用户推荐生成最热榜单。看抽屉新热榜,每日热门、有趣资讯尽收眼底。            " />        <meta name="robots" content="index,follow" />        <meta name="GOOGLEBOT" content="index,follow" />        <meta name="Author" content="搞笑" />        <meta http-equiv="X-UA-Compatible" content="IE=EmulateIE8">        <link type="image/x-icon" href="/images/chouti.ico" rel="icon"/>        <link type="image/x-icon" href="/images/chouti.ico" rel="Shortcut Icon"/>        <link type="image/x-icon" href="/images/chouti.ico" rel="bookmark"/>    <link type="application/opensearchdescription+xml"          href="opensearch.xml" title="抽屉新热榜" rel="search" />二、需要用户登录或者验证的请求 获取这种页面时,我们首先要了解整个登录过程,一般登录过程是,当用户第一次访问时,会自动在浏览器生成cookie文件,当用户输入登录信息后会携带着生成的cookie文件,如果登录信息正确会给这个cookie 授权,授权后以后访问需要登录的页面时携带授权后cookie即可 1、首先访问一下首页,然后查看是否有自动生成cookie #!/usr/bin/env python# -*- coding:utf8 -*-import requests     #导入模拟浏览器请求模块### 1、在没登录之前访问一下首页,获取cookiei1 = requests.get(    url="http://dig.chouti.com/",    headers={'Referer': 'http://dig.chouti.com/'})i1.encoding = "utf-8"                               #http请求编码i1_cookie = i1.cookies.get_dict()print(i1_cookie)                                    #返回获取到的cookie#返回:{'JSESSIONID': 'aaaTztKP-KaGLbX-T6R0v', 'gpsd': 'c227f059746c839a28ab136060fe6ebe', 'route': 'f8b4f4a95eeeb2efcff5fd5e417b8319'}可以看到生成了cookie,说明如果登陆信息正确,后台会给这里的cookie授权,以后访问需要登录的页面携带授权后的cookie即可 2、让程序自动去登录授权cookie 首先我们用浏览器访问登录页面,随便乱输入一下登录密码和账号,获取登录页面url,和登录所需要的字段 携带cookie登录授权 #!/usr/bin/env python# -*- coding:utf8 -*-import requests     #导入模拟浏览器请求模块### 1、在没登录之前访问一下首页,获取cookiei1 = requests.get(    url="http://dig.chouti.com/",    headers={'Referer':'http://dig.chouti.com/'})i1.encoding = "utf-8"                               #http请求编码i1_cookie = i1.cookies.get_dict()print(i1_cookie)                                    #返回获取到的cookie#返回:{'JSESSIONID': 'aaaTztKP-KaGLbX-T6R0v', 'gpsd': 'c227f059746c839a28ab136060fe6ebe', 'route': 'f8b4f4a95eeeb2efcff5fd5e417b8319'}### 2、用户登陆,携带上一次的cookie,后台对cookie中的随机字符进行授权i2 = requests.post(    url="http://dig.chouti.com/login",              #登录url    data={                                          #登录字段        'phone': "8615284816568",        'password': "279819",        'oneMonth': ""    },    headers={'Referer':'http://dig.chouti.com/'},    cookies=i1_cookie                               #携带cookie)i2.encoding = "utf-8"dluxxi = i2.textprint(dluxxi)                                       #查看登录后服务器的响应#返回:{"result":{"code":"9999", "message":"", "data":{"complateReg":"0","destJid":"cdu_50072007463"}}}  登录成功3、登录成功后,说明后台已经给cookie授权,这样我们访问需要登录的页面时,携带这个cookie即可,比如获取个人中心 #!/usr/bin/env python# -*- coding:utf8 -*-import requests     #导入模拟浏览器请求模块### 1、在没登录之前访问一下首页,获取cookiei1 = requests.get(    url="http://dig.chouti.com/",    headers={'Referer':'http://dig.chouti.com/'})i1.encoding = "utf-8"                               #http请求编码i1_cookie = i1.cookies.get_dict()print(i1_cookie)                                    #返回获取到的cookie#返回:{'JSESSIONID': 'aaaTztKP-KaGLbX-T6R0v', 'gpsd': 'c227f059746c839a28ab136060fe6ebe', 'route': 'f8b4f4a95eeeb2efcff5fd5e417b8319'}### 2、用户登陆,携带上一次的cookie,后台对cookie中的随机字符进行授权i2 = requests.post(    url="http://dig.chouti.com/login",              #登录url    data={                                          #登录字段        'phone': "8615284816568",        'password': "279819",        'oneMonth': ""    },    headers={'Referer':'http://dig.chouti.com/'},    cookies=i1_cookie                               #携带cookie)i2.encoding = "utf-8"dluxxi = i2.textprint(dluxxi)                                       #查看登录后服务器的响应#返回:{"result":{"code":"9999", "message":"", "data":{"complateReg":"0","destJid":"cdu_50072007463"}}}  登录成功### 3、访问需要登录才能查看的页面,携带着授权后的cookie访问shouquan_cookie = i1_cookiei3 = requests.get(    url="http://dig.chouti.com/user/link/saved/1",    headers={'Referer':'http://dig.chouti.com/'},    cookies=shouquan_cookie                        #携带着授权后的cookie访问)i3.encoding = "utf-8"print(i3.text)                                     #查看需要登录才能查看的页面 获取需要登录页面的html源码成功 全部代码 get()方法,发送get请求encoding属性,设置请求编码cookies.get_dict()获取cookiespost()发送post请求text获取服务器响应信息 #!/usr/bin/env python# -*- coding:utf8 -*-import requests     #导入模拟浏览器请求模块### 1、在没登录之前访问一下首页,获取cookiei1 = requests.get(    url="http://dig.chouti.com/",    headers={'Referer':'http://dig.chouti.com/'})i1.encoding = "utf-8"                               #http请求编码i1_cookie = i1.cookies.get_dict()print(i1_cookie)                                    #返回获取到的cookie#返回:{'JSESSIONID': 'aaaTztKP-KaGLbX-T6R0v', 'gpsd': 'c227f059746c839a28ab136060fe6ebe', 'route': 'f8b4f4a95eeeb2efcff5fd5e417b8319'}### 2、用户登陆,携带上一次的cookie,后台对cookie中的随机字符进行授权i2 = requests.post(    url="http://dig.chouti.com/login",              #登录url    data={                                          #登录字段        'phone': "8615284816568",        'password': "279819",        'oneMonth': ""    },    headers={'Referer':'http://dig.chouti.com/'},    cookies=i1_cookie                               #携带cookie)i2.encoding = "utf-8"dluxxi = i2.textprint(dluxxi)                                       #查看登录后服务器的响应#返回:{"result":{"code":"9999", "message":"", "data":{"complateReg":"0","destJid":"cdu_50072007463"}}}  登录成功### 3、访问需要登录才能查看的页面,携带着授权后的cookie访问shouquan_cookie = i1_cookiei3 = requests.get(    url="http://dig.chouti.com/user/link/saved/1",    headers={'Referer':'http://dig.chouti.com/'},    cookies=shouquan_cookie                        #携带着授权后的cookie访问)i3.encoding = "utf-8"print(i3.text)                                     #查看需要登录才能查看的页面注意:如果登录需要验证码,那就需要做图像处理,根据验证码图片,识别出验证码,将验证码写入登录字段

June 12, 2019 · 1 min · jiezi

2web爬虫scrapy模块以及相关依赖模块安装

【百度云搜索,搜各种资料:http://bdy.lqkweb.com】【搜网盘,搜各种资料:http://www.swpan.cn】当前环境python3.5 ,windows10系统 Linux系统安装 在线安装,会自动安装scrapy模块以及相关依赖模块 pip install Scrapy手动源码安装,比较麻烦要自己手动安装scrapy模块以及依赖模块 安装以下模块 1、lxml-3.8.0.tar.gz (XML处理库) 2、Twisted-17.5.0.tar.bz2 (用Python编写的异步网络框架) 3、Scrapy-1.4.0.tar.gz (高级web爬行和web抓取框架) 4、pyOpenSSL-17.2.0.tar.gz (OpenSSL库) 5、queuelib-1.4.2.tar.gz (Queuelib是用于Python的持久(基于磁盘的)队列的集合) 6、w3lib-1.17.0.tar.gz (与web相关的函数的Python库) 7、cryptography-2.0.tar.gz (密码学是一种包) 8、pyasn1-0.2.3.tar.gz (ASN类型和编解码器) 9、pyasn1-modules-0.0.9.tar.gz (ASN的集合。基于协议模块) 10、cffi-1.10.0.tar.gz (用于Python调用C代码的外部函数接口) 11、asn1crypto-0.22.0.tar.gz (快速的ASN一个解析器和序列化器) 12、idna-2.5.tar.gz (应用程序中的国际化域名(IDNA)) 13、pycparser-2.18.tar.gz (C解析器在Python中) windows系统安装 windows安装,首先要安装pywin32,根据自己的系统来安装32位还是64位 pywin32-221.win32-py3.5.exe pywin32-221.win-amd64-py3.5.exe 在线安装 pip install scrapy手动源码安装,比较麻烦要自己手动安装scrapy模块以及依赖模块 安装以下模块 1、lxml-3.8.0.tar.gz (XML处理库) 2、Twisted-17.5.0.tar.bz2 (用Python编写的异步网络框架) 3、Scrapy-1.4.0.tar.gz (高级web爬行和web抓取框架) 4、pyOpenSSL-17.2.0.tar.gz (OpenSSL库) 5、queuelib-1.4.2.tar.gz (Queuelib是用于Python的持久(基于磁盘的)队列的集合) 6、w3lib-1.17.0.tar.gz (与web相关的函数的Python库) 7、cryptography-2.0.tar.gz (密码学是一种包) 8、pyasn1-0.2.3.tar.gz (ASN类型和编解码器) 9、pyasn1-modules-0.0.9.tar.gz (ASN的集合。基于协议模块) 10、cffi-1.10.0.tar.gz (用于Python调用C代码的外部函数接口) 11、asn1crypto-0.22.0.tar.gz (快速的ASN一个解析器和序列化器) 12、idna-2.5.tar.gz (应用程序中的国际化域名(IDNA)) 13、pycparser-2.18.tar.gz (C解析器在Python中) 测试是否安装成功 ...

June 12, 2019 · 1 min · jiezi

给你的个人微信朋友圈数据生成一本电子书吧

[toc] 给你的个人微信朋友圈数据生成一本电子书吧!简介微信朋友圈保留着你的数据,它留住了美好的回忆,记录了我们成长的点点滴滴。发朋友圈从某种意义上来讲是在记录生活,感受生活,并从中看到了每个人每一步的成长。 这么一份珍贵的记忆,何不将它保存下来呢?只需一杯咖啡的时间,即可一键打印你的朋友圈。它可以是纸质书,也可以是电子书,可以长久保存,比洗照片好,又有时间足迹记忆。 这本书,可以用来:送给孩子的生日礼物送给伴侣的生日礼物送给未来的自己……现在,你可以选择打印电子书或者纸质书。打印纸质书的话,可以找第三方机构花钱购买;打印电子书的话,我们完全可以自己动手生成,这可以省下一笔不小的开支。 部分截图在开始写代码思路之前,我们先看看最终生成的效果。 电子书效果 纸质书效果 代码思路获取微信书链接看完效果图之后,开始进入代码编写部分。首先,由于朋友圈数据的隐私性较高,手动获取的话,需要使用root的安卓手机进行解密或对pc端备份的聊天记录数据库进行解密,这对大部分人来说难度较大。所以我们采取的思路是基于现有的数据进行打印电子书。 目前,已经有第三方服务支持导出朋友圈数据,微信公众号【出书啦】就提供了这样一种服务。这种服务很大可能性是基于安卓模拟器进行自动化采取操作的,具体就不详细讲了。 首先,关注该公众号,然后开始制作微信书。该过程为小编添加你为好友,然后你将朋友圈开放给他看,等一会后采集完毕后,小编会发给你一个专属链接,这个链接里面的内容就是你的个人朋友圈数据。 生成电子书有了这个链接后,我们开始对该页面的内容进行打印。 整个过程基于selenium自动化操作,如果你有了解过selenium的话,那么其实该过程是很简单的。 首先,引导用户输入微信书链接,我们采用在浏览器弹出一个输入文本框的形式让用户输入数据。首先,在selenium中执行js代码,js代码中完成弹出输入文本框的功能。 输入微信书链接# 以网页输入文本框形式提示用户输入url地址def input_url(): # js脚本 random_id = [str(random.randint(0, 9)) for i in range(0,10)] random_id = "".join(random_id) random_id = 'id_input_target_url_' + random_id js = """ // 弹出文本输入框,输入微信书的完整链接地址 target_url = prompt("请输入微信书的完整链接地址","https://"); // 动态创建一个input元素 input_target_url = document.createElement("input"); // 为其设置id,以便在程序中能够获取到它的值 input_target_url.id = "id_input_target_url"; // 插入到当前网页中 document.getElementsByTagName("body")[0].appendChild(input_target_url); // 设置不可见 document.getElementById("id_input_target_url").style.display = 'none'; // 设置value为target_url的值 document.getElementById("id_input_target_url").value = target_url """ js = js.replace('id_input_target_url', random_id) # 执行以上js脚本 driver.execute_script(js)上述js代码的具体步骤为:弹出一个输入文本框,创建一个动态元素,随机命名该元素的id,并将这个动态元素插入到当前页面中,使得可以在python中通过selenium获取到输入文本框的内容。 ...

June 7, 2019 · 2 min · jiezi

爬虫数据库一些简单的设计逻辑

场景:爬取某商城的部分商品。 队列设计这里至少需要爬取2种资源,一种是商品列表,一种是商品信息。所以要设计1条队列,保存商品信息URL。 爬虫1定期爬前N个列表页 URL,把里面的商品信息URL爬下来,保存到队列里。 爬虫2定期从队列中抽出商品信息URL,爬取商品信息,爬完后把该URL移出队列。 所以呢,简单来说,只要有2张表就行了,一张保存队列信息,一张保存商品信息。 何时停止问题为了避免每次都把所有商品爬一遍,就要在适当的时候停止。爬列表页的时候,一般是设定只爬前 N 页。爬商品信息URL的时候,一般是先检查这个商品是否存在,不存在就入队,存在的话,就表示接下来都是旧数据了,可以停止了。 当然有种情况,就是有些旧的商品,会被人为地置顶,或者排到前面来。 这时候就要设置一个值 M,每次最多爬前 M 个,多了不爬。 数据更新问题:有新商品进来,直接插入即可,如果是旧商品,那要不要更新数据库里的内容呢?一般来说是可以更新的,但有种情况例外,就是你的数据库会有人去编辑的情况。 如果你的数据库有专人编辑,那么最好不要更新旧商品,因为会覆盖掉编辑的内容。并且,数据表要采用软删除的方式,避免前面的人刚删除了数据,你的爬虫又把数据写进去了。

June 4, 2019 · 1 min · jiezi

nodejs来爬取智联全国的竞争最激烈的前十岗位

node爬虫什么是爬虫呢,是一种按照一定的规则,自动地抓取万维网信息的程序或者脚本。为什么选用node呢,因为我是前端,当然要用js实现。 项目分析爬取http://top.zhaopin.com 智联网站上的全国的竞争最激烈三个月内前十的岗位。不需要定时爬取。使用request和cheerio模块。node版本7.6.0、npm版本4.1.2 安装npm install request cheerio -Srequest 模块是一个简化的HTTP客户端。cheerio 模块专为服务器设计的核心jQuery的快速,灵活和精益的实现。可以把爬到的内容和jQuery一样使用。 核心代码// app.jsconst request = require('request');const cheerio = require('cheerio');// 发起请求request('http://top.zhaopin.com', (error, response, body) => { if(error){ console.error(error); } let json = {}; // 获取到的内容放到cheerio模块 const $ = cheerio.load(body); // jQuery 遍历 #hotJobTop .topList li 是通过http://top.zhaopin.com 分析页面结构得到的 $('#hotJobTop .topList li').each(function (index) { let obj = json[index] = {}; obj.name = $(this).find('.title').text().trim(); obj.num = $(this).find('.paddingR10').text().trim(); }); // 打印数据 console.log(json);});执行 node app.js 就会得到如下结果。 ...

June 4, 2019 · 1 min · jiezi

Python爬取-工控行业系统漏洞

先贴连接,让各位观众老爷看看,对不对你们的胃口 工控行业系统漏洞 可以看到,这个网页是html静态的,所以问题变的非常的简单 只需要用request请求网页就可以了 话不多说,直接贴代码 import requestsfrom urllib.parse import urlencodefrom lxml import etreeimport pymysqlimport timeimport xlwtimport xlrddef makeurl(): # http://ics.cnvd.org.cn/?tdsourcetag=s_pctim_aiomsg&max=20&offset=0 baseurl = 'http://ics.cnvd.org.cn/?' params = { 'tdsourcetag': 's_pctim_aiomsg', 'max': '20' } for page in range(MAX_PAGE): params['offset'] = page * 20 url = baseurl + urlencode(params) print('url is ', url) yield urldef get_page_urllist(url): headers = { 'Host': 'ics.cnvd.org.cn', 'Referer': 'http://ics.cnvd.org.cn/?tdsourcetag=s_pctim_aiomsg&max=20&offset=40', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36' } response = requests.get(url, headers=headers) return response.textdef parse_urllist(content): html = etree.HTML(content) for li in html.xpath('//tbody[@id="tr"]/tr'): yield li.xpath('td/a/@href')[0]def get_page(url): headers = { 'Host': 'www.cnvd.org.cn', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36' } response = requests.get(url, headers=headers) return response.textdef parse_page(content, url): html = etree.HTML(content) item = {} item['url'] = url item['标题'] = str(html.xpath('//div[@class="blkContainerSblk"]/h1/text()')[0]) item['CNVD_ID'] = ''.join( [i.strip() for i in html.xpath('//tbody/tr/td[text()="CNVD-ID"]/following-sibling::*[1]//text()')]) item['公开日期'] = ''.join( [i.strip() for i in html.xpath('//tbody/tr/td[text()="公开日期"]/following-sibling::*[1]//text()')]) item['危害级别'] = ''.join([i.strip().replace(' ', '').replace('\r', '').replace('\n', '').replace('\t', '') for i in html.xpath('//tbody/tr/td[text()="危害级别"]/following-sibling::*[1]//text()')]) item['影响产品'] = ''.join( [i.strip() for i in html.xpath('//tbody/tr/td[text()="影响产品"]/following-sibling::*[1]//text()')]) try: item['BUGTRAQ_ID'] = ''.join( [i.strip() for i in html.xpath('//tbody/tr/td[text()="BUGTRAQ ID"]/following-sibling::*[1]//text()')]) except: item['BUGTRAQ_ID'] = '' item['CVE_ID'] = ''.join( [i.strip() for i in html.xpath('//tbody/tr/td[text()="CVE ID"]/following-sibling::*[1]//text()')]) + ' ' + ''.join( [i.strip() for i in html.xpath('//tbody/tr/td[text()="CVE ID"]/following-sibling::*[1]//@href')]) item['漏洞描述'] = ''.join( [i.strip() for i in html.xpath('//tbody/tr/td[text()="漏洞描述"]/following-sibling::*[1]//text()')]) item['漏洞类型'] = ''.join( [i.strip() for i in html.xpath('//tbody/tr/td[text()="漏洞类型"]/following-sibling::*[1]//text()')]) item['参考链接'] = ''.join( [i.strip() for i in html.xpath('//tbody/tr/td[text()="参考链接"]/following-sibling::*[1]//text()')]) item['漏洞解决方案'] = ''.join( [i.strip() for i in html.xpath('//tbody/tr/td[text()="漏洞解决方案"]/following-sibling::*[1]//text()')]) item['厂商补丁'] = ''.join( [i.strip() for i in html.xpath( '//tbody/tr/td[text()="厂商补丁"]/following-sibling::*[1]//text()')]) + ' http://www.cnvd.org.cn' + ''.join( [i.strip() for i in html.xpath('//tbody/tr/td[text()="厂商补丁"]/following-sibling::*[1]//@href')]) item['验证信息'] = ''.join( [i.strip() for i in html.xpath('//tbody/tr/td[text()="验证信息"]/following-sibling::*[1]//text()')]) item['报送时间'] = ''.join( [i.strip() for i in html.xpath('//tbody/tr/td[text()="报送时间"]/following-sibling::*[1]//text()')]) item['收录时间'] = ''.join( [i.strip() for i in html.xpath('//tbody/tr/td[text()="收录时间"]/following-sibling::*[1]//text()')]) item['更新时间'] = ''.join( [i.strip() for i in html.xpath('//tbody/tr/td[text()="更新时间"]/following-sibling::*[1]//text()')]) item['漏洞附件'] = ''.join( [i.strip() for i in html.xpath('//tbody/tr/td[text()="漏洞附件"]/following-sibling::*[1]//text()')]) return itemdef save_data(index, item, workbook): sheet = workbook.get_sheet('sheet1') # 创建一个sheet表格 for col, value in enumerate(item.values()): sheet.write(index, col, value) workbook.save(filename) print('保存成功')def excel_prepare(heads): workbook = xlwt.Workbook() sheet = workbook.add_sheet('sheet1', cell_overwrite_ok=True) # 创建一个sheet表格 for col, value in enumerate(heads): sheet.write(0, col, value) return workbookdef urlisexist(url, urlset): if url in urlset: return True else: return Falsedef getallurl(filename): workbook = xlrd.open_workbook(filename) sheet1 = workbook.sheet_by_name('sheet1') results = sheet1.col_values(0, 1) return resultsdef read_old(filename): workbook = xlrd.open_workbook(filename) sheet1 = workbook.sheet_by_name('sheet1') alloldset = [] for index in range(sheet1.nrows): alloldset.append(sheet1.row_values(index)) return alloldset, sheet1.nrowsdef save_old(index, olditem): sheet = workbook.get_sheet('sheet1') # 创建一个sheet表格 for col, value in enumerate(olditem): sheet.write(index, col, value) workbook.save(filename)if __name__ == '__main__': # http://ics.cnvd.org.cn/?tdsourcetag=s_pctim_aiomsg&max=20&offset=0 # 睡眠时间 TIMESLEEP = 0 filename = '工程控制系统漏洞.xls' MAX_PAGE = 96 heads = ['url', '标题', 'CNVD_ID', '公开日期', '危害级别', '影响产品', 'BUGTRAQ_ID', 'CVE_ID', '漏洞描述', '漏洞类型', '参考链接', '漏洞解决方案', '厂商补丁', '验证信息', '报送时间', '收录时间', '更新时间', '漏洞附件'] try: alloldset, length = read_old(filename) except: alloldset = [] length = 1 workbook = excel_prepare(heads) for index, olditem in enumerate(alloldset): save_old(index, olditem) try: urlset = getallurl(filename) except: urlset = [] index = length for urlofpage in makeurl(): pagelistcontent = get_page_urllist(urlofpage) for url in parse_urllist(pagelistcontent): print('url is >>>', url) if not urlisexist(url, urlset): time.sleep(TIMESLEEP) result = get_page(url) item = parse_page(result, url) print('item is >>>', item) save_data(index, item, workbook) index = index + 1 workbook.save(filename)不懂的地方,下方评论提问 ...

June 3, 2019 · 3 min · jiezi

爬取京东生鲜的商品数据和评论数据

很久以前,爬了京东的生鲜页面,鞋子想把代码发出来共享 首先简明,爬取 商品数据 采用selenium操作chrome模拟浏览器动态渲染页面+ajax加载评论 具体的看下面的说明 所需内容:商品小分类名称(苹果,橙子等)商品名称(烟台红富士苹果 5kg 一级铂金大果 单果230-320g 新鲜水果)商品总评论数量商品好评率评论星级评论长度评论点赞数量评论回复数量评论文本内容评论者等级评论发表距抓取的天数(days)抓取部分带有追评的评论:追评文本内容、追评与初评相距时间以上是这次任务的需求 这个页面大部分的信息都是动态渲染出来的,所以要用selenium 可以看到,要找到评论不是去常见的HXR而是JS,peoduct开头的就是评论信息 Request URL: https://sclub.jd.com/comment/...Request Method: GETStatus Code: 200 Remote Address: 117.148.129.129:443Referrer Policy: no-referrer-when-downgrade在这个url中,查询字符串中的大部分参数不是必须的 def make_url(baseurl, page=0, score=0, productId='3756271'): data1 = { 'callback': 'fetchJSON_comment98vv7490', 'productId': productId, 'score': score, 'sortType': '6', 'page': page, 'pageSize': '10', 'isShadowSku': '0', # 'fold': '1', # } url = baseurl + urlencode(data1) return url具体的可以在代码中体现。 下面我要贴代码了,坐稳扶好,不想复制的话,可以去我的github上下载。 # https://www.jd.com/allSort.aspximport requestsfrom pyquery import PyQuery as pqfrom prettyprinter import cpprintimport jsonfrom urllib.parse import urlencodefrom selenium import webdriverfrom selenium.webdriver.support.wait import WebDriverWaitfrom selenium.webdriver.common.by import Byfrom selenium.webdriver.support import expected_conditions as ECimport timeimport csvimport datetimeimport sysdef get_ajax(url): headers = { 'referer': 'https://item.jd.com/3756271.html', # referer: https://item.jd.com/3756271.html 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36', } response = requests.get(url, headers=headers) return json.loads(response.text[26:-2])def make_url(baseurl, page=0, score=0, productId='3756271'): data1 = { 'callback': 'fetchJSON_comment98vv7490', 'productId': productId, 'score': score, 'sortType': '6', 'page': page, 'pageSize': '10', 'isShadowSku': '0', # 'fold': '1', # } url = baseurl + urlencode(data1) return urldef parse_json(rjson, url=None): for comment in rjson.get('comments'): item = {} item['url'] = url item['评论星级'] = comment.get('score') item['评论长度'] = len(comment.get('content')) item['评论点赞数量'] = comment.get('usefulVoteCount') item['评论回复数量'] = comment.get('replyCount') item['评论文本内容'] = comment.get('content') item['评论者等级'] = comment.get('userLevelId') try: date1 = time.strptime(comment.get('creationTime'), "%Y-%m-%d %H:%M:%S") date2 = time.localtime(time.time()) date1 = datetime.datetime(date1[0], date1[1], date1[2]) date2 = datetime.datetime(date2[0], date2[1], date2[2]) item['评论发表距抓取的天数(days)'] = str((date2 - date1).days) except Exception as error: print('error is >>>', error) item['评论发表距抓取的天数(days)'] = '' if comment.get('afterUserComment', {}).get('hAfterUserComment', {}).get('content', '') == '此用户未填写评价内容': item['追评文本内容'] = '' else: item['追评文本内容'] = comment.get('afterUserComment', {}).get('hAfterUserComment', {}).get('content', '') try: date1 = time.strptime(comment.get('afterUserComment', {}).get('created', ''), "%Y-%m-%d %H:%M:%S") date2 = time.localtime(time.time()) date1 = datetime.datetime(date1[0], date1[1], date1[2]) date2 = datetime.datetime(date2[0], date2[1], date2[2]) item['追评与初评相距时间'] = str((date2 - date1).days) except Exception: item['追评与初评相距时间'] = '' if item['追评文本内容'] == '': item['追评与初评相距时间'] = '' yield itemdef save_csv_merinfo(item): with open(FILENAME_MER, 'a', encoding=ENCODING, newline='') as f: writer = csv.DictWriter(f, fieldnames=fieldnames_merinfo) # writer.writeheader() writer.writerow(item)def save_csv_cominfo(item): with open(FILENAME_COM, 'a', encoding=ENCODING, newline='') as f: writer = csv.DictWriter(f, fieldnames=fieldnames_cominfo) # writer.writeheader() writer.writerow(item)def get_page(url): browser.get(url) submit = wait.until(EC.presence_of_element_located((By.XPATH, '//div[contains(@class,"tab-main")]/ul/li[5]'))) time.sleep(2) for i in range(30): browser.execute_script("window.scrollBy(0,50)") time.sleep(0.1) submit.click() time.sleep(3) return browser.page_sourcedef parse_page(html, url): page_item = {} doc = pq(html, parser='html') page_item['url'] = url page_item['商品小分类名称'] = doc('#crumb-wrap > div > div.crumb.fl.clearfix > div:nth-child(5) > a').text() page_item['商品名称'] = doc('div.itemInfo-wrap div.sku-name').text() page_item['商品总评论数量'] = doc('#detail > div.tab-main.large > ul > li.current > s').text().replace('(', '').replace( ')', '') page_item['商品好评率'] = doc('#comment > div.mc > div.comment-info.J-comment-info > div.comment-percent > div').text() ##comment > div.mc > div.comment-info.J-comment-info > div.comment-percent > div return page_itemdef csv_create(): with open(FILENAME_MER, 'w', encoding=ENCODING, newline='') as f: writer = csv.writer(f) writer.writerow(fieldnames_merinfo) with open(FILENAME_COM, 'w', encoding=ENCODING, newline='') as f: writer = csv.writer(f) writer.writerow(fieldnames_cominfo)def crawl_all_page_url(): global ALL_PAGE_URL browser = webdriver.Chrome() wait = WebDriverWait(browser, 20) browser.get('https://www.jd.com/allSort.aspx') wait.until(EC.presence_of_element_located( (By.XPATH, '/html/body/div[5]/div[2]/div[1]/div[2]/div[2]/div[9]/div[2]/div[3]'))) CASE = [] for i in range(10): # 水果 initcase = '/html/body/div[5]/div[2]/div[1]/div[2]/div[2]/div[9]/div[2]/div[3]/dl[2]/dd/a[{}]'.format(i + 1) CASE.append(initcase) for i in range(4): # 猪肉羊肉 initcase = '/html/body/div[5]/div[2]/div[1]/div[2]/div[2]/div[9]/div[2]/div[3]/dl[3]/dd/a[{}]'.format(i + 1) CASE.append(initcase) for i in range(8): # 海鲜水产 initcase = '/html/body/div[5]/div[2]/div[1]/div[2]/div[2]/div[9]/div[2]/div[3]/dl[4]/dd/a[{}]'.format(i + 1) CASE.append(initcase) for i in range(4): # 禽肉蛋白 initcase = '/html/body/div[5]/div[2]/div[1]/div[2]/div[2]/div[9]/div[2]/div[3]/dl[5]/dd/a[{}]'.format(i + 1) CASE.append(initcase) for i in range(6): # 冷冻食品 initcase = '/html/body/div[5]/div[2]/div[1]/div[2]/div[2]/div[9]/div[2]/div[3]/dl[6]/dd/a[{}]'.format(i + 1) CASE.append(initcase) # 规则只要更改range里面的值和dl[]里面的值,可高度扩展 for case in CASE: print('>>>>>>>>>') submit = wait.until(EC.element_to_be_clickable( (By.XPATH, case))) submit.click() print(browser.current_url) handle = browser.current_window_handle handles = browser.window_handles for newhandle in handles: if newhandle != handle: browser.switch_to.window(newhandle) time.sleep(1.5) wait.until(EC.presence_of_element_located((By.XPATH, '//div[@id="plist"]/ul[contains(@class,"gl-warp")]'))) doc = pq(browser.page_source, parser='html') for li in list(doc('div#plist ul.gl-warp li').items())[:10]: res = 'https:' + str(li('div div.p-commit-n strong a').attr('href')).replace('#comment', '') print(res) ALL_PAGE_URL.append(res) time.sleep(1.5) browser.close() browser.switch_to.window(handle)def load_all_page_url(): global ALL_PAGE_URL with open(FILENAME_CACHE, 'r', encoding='utf-8') as f: reader = csv.reader(f) for item in reader: ALL_PAGE_URL.append(item[0])if __name__ == '__main__': # 前期准备>>>>>>>>>> browser = webdriver.Chrome() # selenium模拟浏览器 wait = WebDriverWait(browser, 20) MAXINDEX = 7 # 最大请求评论页数,为了控制评论数量在500条左右,应该设置为35左右,35时略大于500(网页评论非无限下拉) # 用户自定义配置区******************************** TIMESLEEP = 2 # 睡眠间隔 FILENAME_MER = 'merinfo_test.csv' # 商品信息的文件名 FILENAME_COM = 'cominfo_test.csv' # 评论信息的文件名 FILENAME_CACHE = 'cache.csv' ENCODING = 'UTF-8' # 保存的CSV的编码 # ********************************************** # csv文件的字段 fieldnames_merinfo = ['url', '商品小分类名称', '商品名称', '商品总评论数量', '商品好评率'] fieldnames_cominfo = ['url', '评论星级', '评论长度', '评论点赞数量', '评论回复数量', '评论文本内容', '评论者等级', '评论发表距抓取的天数(days)', '追评文本内容', '追评与初评相距时间'] # <<<<<<<<<<<<<<<<< start = time.time() # csv_create() # 重置 # 去重模块>>> URLSET = [] # 已存在的url的集合 with open(FILENAME_MER, 'r', encoding=ENCODING) as f: reader = csv.reader(f) for res in reader: URLSET.append(res[0]) print('URLSET is', URLSET) # 爬取商品信息 ALL_PAGE_URL = [] # 所有的网页链接 load_all_page_url() # 这两个函数要二选一,load_all_page_url会从本地的cache.csv载入,速度更快,脱机工作,不占用网络 # crawl_all_page_url() # 这两个函数要二选一,load_all_page_url会从本地的cache.csv载入,速度更快,脱机工作,不占用网络 for page_url in ALL_PAGE_URL: if page_url not in URLSET: URLSET.append(page_url) # 动态去重 try: html = get_page(page_url) # 请求网页,selenium动态渲染 item_mer = parse_page(html, url=page_url) # 解析网页,pyquery cpprint(item_mer) # 爬取评论信息,ajax Flag = 0 # 计数器 ITEMS = [] baseurl = 'https://sclub.jd.com/comment/productPageComments.action?' for score in [5, 3, 2, 1]: # 0全部评论,5追评,3好评,2中评,1差评 if score == 5: MAXINDEX_TEMP = MAXINDEX else: MAXINDEX_TEMP = int(MAXINDEX / 7) # 控制比例为7:1:1:1 for index in range(MAXINDEX_TEMP): time.sleep(TIMESLEEP) url = make_url(baseurl, page=index, score=score, productId=''.join(list(filter(str.isdigit, page_url)))) # 构造url try: json_ = get_ajax(url) # 进行ajax请求 if len(json_.get('comments')) != 0: for item in parse_json(json_, url=page_url): # 解析json cpprint(item) ITEMS.append(item) Flag += 1 else: break except Exception as error: print('AJAX请求发生错误{}>>>'.format(error)) print('url is {}'.format(url)) print(str(datetime.datetime.now())) sys.exit(0) # ajax请求出错时退出程序,确保数据完整性 # 一个网页的商品信息和评论信息都爬取完毕时,保存数据 save_csv_merinfo(item_mer) # 保存商品信息 for item in ITEMS: # 保存评论信息 try: save_csv_cominfo(item) except Exception as error: print(error) print("保存了{}条评论".format(Flag)) except Exception as error: print('网页请求发生错误{}>>>'.format(error)) print('一个网页请求已经结束>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>') # time.sleep(TIMESLEEP) end = time.time() print('总共用时{}秒'.format(end - start))还是写了一些注释的,不懂得可以在评论中问。暂时这样吧! ...

June 3, 2019 · 4 min · jiezi

数据采集的另一种思路-浏览器脚本注入

昨天想去极客时间把购买的一个专栏里的数据扒下来,发现之前写的python脚本不能用了,原因是他们网站做了限流、也加了http时间戳的一些校验。我们可以将之前的python脚本进行改进,用ip代理池来处理限流,寻找时间戳验证的规则就可以解决。但是这次我们用了另外的一种爬虫的思路,就是我们直接写一些js脚本,在对方的网站里运行,去请求相应的接口,从而得到想要的数据。 这种思路其实见过很多例子,之前有一个很火的,qq空间自动点赞的脚本,看过它的源码,其实很简单,就是直接去操作dom,然后触发一些事件。 另外一个很火的例子,github上很火的一个repo, fuckZhihu,据说是winter当年退知乎时写的,将自己在知乎的数据保存下来。 下面是这次实践的内容: 获取文章id集合刚进入专栏的时候会有一个获取左侧文章列表集合的请求,在这个接口里,我们就能获取到当前专栏的所有请求。 这个专栏大概有50多篇文章,因为限流的原因,我们分成两次进行请求。 注入FileSaver.jsFileSaver是一个运行在浏览器中,将数据下载为json或者excel文件的库。 我们在这里创建一个script标签,并将这个标签插入到文档中。 我在这里写了一个方法downloadJson,我们将等会获取到的数据传到这里来,就可以下载这个json文件了。 创建请求创建ajax请求,请求文章详情的接口。 这里我们用原生的js来写的,是一个post请求,res就是我们得到这个接口的返回值,我们将需要的数据从这个返回值中取出来就可以了。 上面说的是单个请求的实现。多个请求的实现如下图所示。 然后我们将数据保存一下: 所有的结果都放在rs这个数组中了。 下载数据我们将所有数据放在了一个数组中,在最后一次请求结束的时候,执行我们写好的downloadJson方法进行下载就可以了。 导入数据库json文件导入数据库网上有很多的工具,我这次是用之前写好的脚本。 这个脚本在我的github上面,是用nodejs写的,地址:tomysql.js 最后我们这次没用通用的做法,模拟请求,或者模拟浏览器,而是直接利用浏览器来采集数据,当然也要根据实际情况去选择用哪种做法。 完整的脚本: geek.js

May 31, 2019 · 1 min · jiezi

爬虫手记-我是如何在3分钟内开发完一个爬虫的

前言开发爬虫是一件有趣的事情。写一个程序,对感兴趣的目标网站发起HTTP请求,获取HTML,解析HTML,提取数据,将数据保存到数据库或者存为CSV、JSON等格式,再用自己熟悉的语言例如Python对这些数据进行分析生成酷炫的图表。这个过程是不是很兴奋? 然而,开发爬虫并不是一件简单的事情。通常开发一个简单爬虫往往需要编写好几个模块:下载器、解析器、提取规则、保存模块。实现这个简单爬虫用Python实现至少需要编写10-20行代码,而且如果考虑并发和调度的话,通常要编写50行代码以上。更麻烦的是,如果要管理多个爬虫实现爬虫的工程化,需要对各个网站的爬虫代码提取共用模块和参数,这个过程需要相当的工程经验和时间积累。其实,一般各大网站的结构大同小异,仅需要更改提取规则即可。很多爬虫工程师要在大型项目中编写成百上千的提取规则,对于没有任何管理工具的人来说,这基本上是个噩梦。 可配置爬虫幸运的是,Crawlab在版本v0.2.1中新增功能可配置爬虫可以让工程师从这些重复性工作中解放开来。Crawlab的可配置爬虫只需要爬虫工程师配置一些必要的CSS/XPath提取规则,就可以完成一个常规的爬虫开发。根据作者实验,对于CSS选择器或XPath稍微熟悉点的工程师,用可配置爬虫开发完一个包含五脏俱全的常规爬虫只需要1-3分钟。 Crawlab的可配置爬虫是基于Scrapy的,因此天生是支持并发的。而且,可配置爬虫完全支持Crawlab自定义爬虫的一般功能的,因此也支持任务调度、任务监控、日志监控、数据分析。 安装运行CrawlabCrawlab是一个专注于爬虫的集成了爬虫管理、任务调度、任务监控、数据分析等模块的分布式爬虫管理平台,非常适合对爬虫管理、爬虫工程化有要求的开发者及企业。 关于Crawlab的详细介绍请参考之前的文章: 爬虫平台Crawlab v0.2发布手把手教你如何用Crawlab构建技术文章聚合平台(二)手把手教你如何用Crawlab构建技术文章聚合平台(一)分布式通用爬虫管理平台Crawlab以下是Crawlab的安装和运行步骤,时间大概在10-20分钟。 安装步骤运行步骤如何开发并运行可配置爬虫下面总算到了爬虫开发时间。这里将以网易24小时排行新闻为例,开发一个相应的可配置爬虫,整个流程应该不超过3分钟。 添加爬虫Crawlab跑起来之后,在浏览器中打开网址http://localhost:8080,导航到爬虫。在点击添加爬虫按钮。 点击可配置爬虫。 输入完基本信息,点击添加。 配置爬虫添加完成后,可以看到刚刚添加的可配置爬虫出现了在最下方,点击查看进入到爬虫详情。 点击配置标签进入到配置页面。接下来,我们需要对爬虫规则进行配置。 这里已经有一些配置好的初始输入项。我们简单介绍一下各自的含义。 抓取类别这也是爬虫抓取采用的策略,也就是爬虫遍历网页是如何进行的。作为第一个版本,我们有仅列表、仅详情页、列表+详情页。 仅列表页。这也是最简单的形式,爬虫遍历列表上的列表项,将数据抓取下来。仅详情页。爬虫只抓取详情页。列表+详情页。爬虫先遍历列表页,将列表项中的详情页地址提取出来并跟进抓取详情页。这里我们选择列表+详情页。 列表项选择器 & 分页选择器列表项的匹和分页按钮的匹配查询,由CSS或XPath来进行匹配。 开始URL爬虫最开始遍历的网址。 遵守Robots协议这个默认是开启的。如果开启,爬虫将先抓取网站的robots.txt并判断页面是否可抓;否则,不会对此进行验证。用户可以选择将其关闭。请注意,任何无视Robots协议的行为都有法律风险。 列表页字段 & 详情页字段这些都是再列表页或详情页中需要提取的字段。字段由CSS选择器或者XPath来匹配提取。可以选择文本或者属性。 在检查完目标网页的元素CSS选择器之后,我们输入列表项选择器、开始URL、列表页/详情页等信息。注意勾选url为详情页URL。 点击保存、预览,查看预览内容。 OK,现在配置大功告成,终于可开始跑爬虫了! 运行爬虫你唯一需要做的,就是点击运行按钮并确认。点击概览标签,你可以看到任务已经开始运行了。 点击创建时间链接导航到任务详情,点击结果标签,你就可以看到抓取到的结果已经保存下来了。 怎么样,这个过程是不是超级简单?如果熟练的话,整个过程可以在60秒内完成!就跟玩魔方一样,越玩越熟练! 结语本文利用Crawlab的可配置爬虫功能实现了3分钟内对网易新闻24小时新闻排行榜的抓取。同样的过程可以实现在其他类似的网站上面。虽然这是一个经典的“列表+详情页”的抓取模式,比较简单,后续我们还会开发更多的更复杂的抓取模式,实现更多的抓取需求。Crawlab的可配置爬虫降低了爬虫的开发时间,增加了爬虫开发效率,完善了工程化水平,将爬虫工程师从日常的繁琐配置工作中解放出来。配置工作可以交给初级爬虫工程师或者外包人员来做,而高级爬虫工程师会把精力放在更复杂的爬虫工作上来,例如反爬、动态内容、分布式爬虫等等。 Github: tikazyq/crawlab 如果感觉Crawlab还不错,对你的日常工作或企业有帮助的话,请加作者微信拉入开发交流群,大家一起交流关于Crawlab的使用和开发。

May 27, 2019 · 1 min · jiezi

Serverless????Nodejs-Puppeteer-渗透测试爬虫实践

本文归纳于微服务与云原生 https://github.com/wx-chevalier/Backend-Series系列文章,其相关的参考资料声明于 Awesome Serverless List。 Serverless????Node.js Puppeteer 渗透测试爬虫实践参考 CNCF 的定义,Serverless 是指构建和运行不需要服务器管理的应用程序的概念;而 AWS 官方对于 Serverless 的介绍是:服务器架构是基于互联网的系统,其中应用开发不使用常规的服务进程。相反,它们仅依赖于第三方服务(例如 AWS Lambda 服务),客户端逻辑和服务托管远程过程调用的组合。 Serverless 目前主要的落地形式为 BaaS 与 FaaS。BaaS 后端即服务,即是一些后端云服务,比如云数据库、对象存储、消息队列等。利用 BaaS,可以极大简化我们的应用开发难度。FaaS 函数即服务,则是暂存容器中运行的自定义代码,函数是无服务器架构中抽象语言运行时的最小单位。在 FaaS 中,用户主要为函数的运行时间付费,而不需要关心 CPU 或 RAM 或任何其他资源及其运维的负担。 参考 BaaS 与 FaaS 的定义,我们可以知道 Serverless 的主要特点有: 事件驱动:函数在 FaaS 平台中,需要通过一系列的事件来驱动函数执行。无状态:因为每次函数执行,可能使用的都是不同的容器,无法进行内存或数据共享。如果要共享数据,则只能通过第三方服务,比如 Redis 等。无运维:使用 Serverless 我们不需要关心服务器,不需要关心运维。这也是 Serverless 思想的核心。按实际使用计费:使用 Serverless 成本很低,因为我们只需要为每次函数的运行付费。函数不运行,则不花钱,也不会浪费服务器资源。这些特征的本质,是用户对于云端应用开发,乃至于所谓云原生应用中的用户友好与低心智负担方向演讲的最直接途径,而这种简单、经济、可信赖的期许,也是云计算的初心。当然 Serverless 并不拘泥于 Function,而是应该多种部署形态并存,譬如以应用方式部署,则是遵循单一职责原则,但是能够触发多个事件;也可以在容器级别部署,能够包含任意语言、任意运行时,譬如 Knative 这样的解法。在微服务与云原生/Serverless 一节中我们也讨论了更多的 Serverless 落地模式。 前端视角的 Serverless在现代 Web 开发基础与工程实践 https://github.com/wx-chevalier/Web-Series 系列的前言中,我们也讨论了 Web 技术与生态经历了模板渲染、前后端分离与单页应用,工程化与微前端,大前端与 Serverless 这三个不同的阶段。自然从前端的视角来看,Serverless 也赋予了前端更多的自由与可能性,在服务端渲染,小程序开发的简单服务端支持,包括 BFF 接口聚合等方面都有很多的空间。 ...

May 21, 2019 · 2 min · jiezi

爬虫再现之妹子图全站爬取之初级版本

序#### 做了好久的爬虫+少量web,突然开始专门做 web 开发稍微有点不太适应,毕竟比起来爬虫相对还是稍稍有点无聊的,但是又不想荒废了自己的爬虫,就准备写点关于爬虫的东西,把以前用过的东西都再过一边,毕竟感觉爬虫跟 web 没有太大的区别,并发,缓存,分布式,消息队列......言归正传 之所以爬取妹子图,一是因为简单方便,比较适合做项目展示,写博客不就是为了展示么?目前就是准备先完整的爬取,再并发(多进程+多线程 or 多线程+协程(asyncio)), 全站爬取(深度优先 or 广度优先), 数据解析(清洗?), 数据去重(redis), 数据储存(MongoDB, Mysql, csv, excel), 定时更新(crontab), 增量爬取....再三声明:本次爬取,会分别使用 requests + scrapy 爬取, 并稍微有点深度的解析 scrapy 框架使用, 并带领喜欢爬虫,以及想要学习爬虫的同学缓缓深入, 爬虫的天堂!在本连载连载期间, 会分别对这些爬虫中所要使用的类库以及工具的重要部分做专门的实例讲解,并单独开一篇文章专门讲解!别急别急,慢慢来。。。。开发环境: win10, Python3.6, fidder, Pycharm开发主要工具库: Requests, BeautifulSoup, Xpath, re, scrapy1. 爬取妹子第一步:浏览并分析目标网站结构目的: 搜索目标数据所在,分析网站结构,设定合理的爬取方案,这块主要是确定行动方案,其他的后面再说。- 进入该网站首页, 看到该网站总共有 8 个大分类, 也就是说我们爬取的数据主要就是这么几个大的分类,首页跟每日更新可以自动过滤.毕竟我们后面也是需要分类的,首页跟每日更新,没有明确的确定分类, 也就是说我们只需要在意中间 6 个即可.如下图所示: 再往下看,会发现在这 6 个分类里面, 都有总页数,可以确定每个分类需要获取的总页数, 并且发现街拍跟自拍这两项跟其他的分类不一样, 这两个直接每个大分类下面都是完整的照片, 而另外四个分类下面, 是有若干个小标题, 也就是说, 这两个分类没有下一层; 接下来点击剩下的四个分类的任意小标题进入, 发现我们要的图片, 就在这里啊!哇咔咔..... 在这里你会发现,这每个标题就是一个组图,这个下面的总页数就是每个组图的照片数量;也就是说图片的链接就是在这个页面获取到的,这就是我们获取图片的地方; 至此,网页结构分析完毕。总结:爬虫大致的过程就是 首页—>获取到分类链接->循环分类->获取每个分类的总页数->循环总页数->获取每个页面的组图主题以及每个组图的总页数->获取图片2.爬取妹子第二步:分析网站 url 结构 跟 请求- 一般网页请求都是 http,https 请求, 抓包工具最合适的是 fidder , 谁要是说花瓶的话,我肯定要喷他的,要是说大鲨鱼,我也会喷他,毕竟他们都不够合适啊;当然, chrome 的 f12 也是抓包分析的利器, F12 适合简单的分析,分析下简单的 url 结构还是可以的, 一般的网站 F12 即可快速搞定;- fidder 抓包原理: fidder 启动后建立一个本地代理服务器,类似 vpn ,默认监听127.0.0.1:8888, 拦截并转发请求;- 关于 fiddler 的抓包方法,以及配置使用什么的, 由于本人太懒,就不打算描述了,看家尽可 百度谷歌必应, 一大把的教程; so easy....目的:拼接所要循环的 url , 分析请求以及反爬抓包分析 url 结构:点击查看分类的第二页, 如下图所示, 可以简单的分析出分类的翻页链接,可以照此拼接出来, 而且页数的数字换成1之后,页面会跳转至分类首页,而不是带页数的链接,但是无论跳不跳转,网页的数据不变,都不影响我们的数据获取; ...

May 21, 2019 · 1 min · jiezi

时隔五年Scrapyd-终于原生支持-basic-auth

Issue in 2014scrapy/scrapyd/issues/43 Pull request in 2019scrapy/scrapyd/pull/326 试用安装:pip install -U git+https://github.com/my8100/scrapyd.git@add_basic_auth更新配置文件 scrapyd.conf,其余配置项详见官方文档[scrapyd]username = yourusernamepassword = yourpassword启动:scrapydIn [1]: import requestsIn [2]: requests.get('http://127.0.0.1:6800/').status_codeOut[2]: 401In [3]: requests.get('http://127.0.0.1:6800/', auth=('admin', 'admin')).status_codeOut[3]: 401In [4]: requests.get('http://127.0.0.1:6800/', auth=('yourusername', 'yourpassword')).status_codeOut[4]: 200由于 Scrapyd 的 GitHub 最新提交已经重构了 Jobs 页面,如果正在使用 ScrapydWeb 管理 Scrapyd,则需同步更新 ScrapydWeb:pip install -U git+https://github.com/my8100/scrapydweb.gitGitHubmy8100/scrapyd

May 9, 2019 · 1 min · jiezi

使用python-scrapy爬取网页中带有地图展示的数据

最近有个需求,是要爬取某个物流公司的官网信息,我看了下官网,基本上都是静态页面比较好抓取,不像那种资讯类,电子商务类型的网站结果复杂,反爬严格,AJAX众多,还内心暗自庆幸,当我进一步分析时候发现并非普通的静态页面。例如这个URL界面,我要获取全中国各大城市的物流园区分布信息,并且要获取详情信息,这个页面里面是有个地图镶嵌,每个城市物流信息你要单独点击地图上的信息才能显示。https://www.glprop.com.cn/our... 我刚开始想,这种会不会是ajax请求呢,通过chrmoe抓包并没有发现,然后我查看网页源代码发现所有城市信息在一个scripts里面如图:然后各个园区的信息在一个叫park={xx}里面存着 原来都在这里面,直接获取源代码,正则匹配,开干。item: #普洛斯class PuluosiNewsItem(scrapy.Item): newstitle=scrapy.Field() newtiems=scrapy.Field() newslink=scrapy.Field()class PuluosiItem(scrapy.Item): assetstitle = scrapy.Field() assetaddress=scrapy.Field() assetgaikuang=scrapy.Field() assetpeople=scrapy.Field() asseturl = scrapy.Field()pipelines: class PuluosiNewsPipeline(object): def __init__(self): self.wb=Workbook() self.ws=self.wb.active #设置表头 self.ws.append(['普洛斯新闻标题','新闻发布时间','新闻URL']) self.wb2 = Workbook() self.ws2 = self.wb2.active self.ws2.append(['资产标题', '资产地址', '资产概况','其他信息','URL']) def process_item(self,item,spider): if isinstance(item, PuluosiNewsItem): line = [item['newstitle'], item['newtiems'], item['newslink']] # 把数据中每一项整理出来 self.ws.append(line) self.wb.save('PuluosiNews.xlsx') # 保存xlsx文件 elif isinstance(item,PuluosiItem): line = [item['assetstitle'], item['assetaddress'], item['assetgaikuang'],item['assetpeople'],item['asseturl']] self.ws2.append(line) self.wb2.save('PuluosiAsset.xlsx') # 保存xlsx文件 return itemspider: ...

April 26, 2019 · 2 min · jiezi

小程序开发一使用scrapy爬虫采集数据

过完年回来,业余时间一直在独立开发一个小程序。主要数据是8000+个视频和10000+篇文章,并且数据会每天自动更新。 我会整理下整个开发过程中遇到的问题和一些细节问题,因为内容会比较多,我会分成三到四篇文章来进行,本文是该系列的第一篇文章,内容偏python爬虫。 本系列文章大致会介绍一下内容: 数据准备(python的scrapy框架)接口准备(nodejs的hapijs框架)小程序开发(mpvue以及小程序自带的组件等)部署上线(小程序安全域名等配置以及爬虫/接口等线上部署维护) 数据获取 数据获取的方法有很多种,这次我们选择了爬虫的方式,当然写一个爬虫也可以用不同的语言,不同的方式。之前写过很多爬虫,这次我们选择了python的scrapy库。关于scrapy,百度百科解释如下: Scrapy,Python开发的一个快速、高层次的屏幕抓取和web抓取框架,用于抓取web站点并从页面中提取结构化的数据。Scrapy用途广泛,可以用于数据挖掘、监测和自动化测试。 学习scrapy,最好的方式就是先阅读一遍文档(Scrapy 1.6 documentation),然后照着文档里的例子写一写,慢慢就熟悉了。里面有几个很重要的概念是必须要理解的: Items 官方对items的定义是“The main goal in scraping is to extract structured data from unstructured sources, typically, web pages.”,个人理解为数据结构,也就是要爬取数据的字段,最好能和数据库字段对应,便于入库。Spiders “Spiders are classes which define how a certain site (or a group of sites) will be scraped, including how to perform the crawl (i.e. follow links) and how to extract structured data from their pages (i.e. scraping items). ”,也就是爬虫比较核心的内容,定义爬虫的方式,一些策略,以及获取那些字段等等。pipelines ...

April 23, 2019 · 1 min · jiezi

阿里云centos7.2下安装chrome浏览器+webdriver+selenium及常见设置-傻瓜教程

Linux版本:阿里云CentOS Linux release 7.2.1511 (Core) root用户下python版本python3.6,python3安装方法https://www.cnblogs.com/FZfangzheng/p/7588944.html 测试时间:2019-04-161.安装chrome浏览器1.1 创建yum源文件cd /etc/yum.repo.d/touch google-chrome.repo1.2 输入yum源信息[google-chrome]name=google-chromebaseurl=http://dl.google.com/linux/chrome/rpm/stable/$basearchenabled=1gpgcheck=1gpgkey=https://dl-ssl.google.com/linux/linux_signing_key.pub1.3 安装google chromeyum -y install google-chrome-stable –nogpgcheck2.安装chromedriver及seleniumyum install chromedriverpip install selenium默认安装路径:chromedriver: /usr/bin/chromedriver3.修改配置来执行代码,及常见错误处理3.1测试demo#!/usr/bin/env python# -- coding=UTF-8 --#测试代码import timefrom selenium import webdriverdef test(): chromeOptions = webdriver.ChromeOptions() chromeOptions.add_argument(’–headless’) #浏览器无窗口加载 chromeOptions.add_argument(’–disable-gpu’) #不开启GPU加速 """ 解决报错: selenium.common.exceptions.WebDriverException: Message: unknown error: Chrome failed to start: exited abnormally (unknown error: DevToolsActivePort file doesn’t exist) """ chromeOptions.add_argument(’–disable-dev-shm-usage’) #禁止 chromeOptions.add_argument(’–no-sandbox’)#以根用户打身份运行Chrome,使用-no-sandbox标记重新运行Chrome #其它设置(可选): #chromeOptions.add_argument(’–hide-scrollbars’) #隐藏滚动条, 应对一些特殊页面 #chromeOptions.add_argument(‘blink-settings=imagesEnabled=false’) #不加载图片, 提升速度 #chromeOptions.add_argument(“user-agent=Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36”) #伪装其它版本浏览器,有时可以解决代码在不同环境上的兼容问题,或者爬虫cookie有效性保持一致需要设置此参数 #创建driver对象 #chrome_options=chromeOptions加载设置 #executable_path="/usr/bin/chromedriver"指定webdriver路径(可选) driver = webdriver.Chrome(chrome_options=chromeOptions,executable_path="/usr/bin/chromedriver") try: driver.get(“http://www.baidu.com”) time.sleep(3) print(driver.page_source) except Exception as e: print(e) finally: driver.quit()if name == ‘main’: test()4.参考资料https://www.cnblogs.com/ianduin/p/8727333.html https://www.cnblogs.com/baijing1/p/9751399.html https://www.cnblogs.com/z-x-y/p/9507467.html ...

April 16, 2019 · 1 min · jiezi

爬虫实现:根据IP地址反查域名

域名解析与IP地址域名解析是把域名指向网站空间IP,让人们通过注册的域名可以方便地访问到网站的一种服务;IP地址是网络上标识站点的数字地址,为了方便记忆,采用域名来代替IP地址标识站点地址。域名解析就是域名到IP地址的转换过程,该过程由DNS服务器完成(来自百度百科)先来了解两个知识点1、一个域名同一时刻只能对应一个IP地址2、一个IP地址可以解析绑定多个域名,没有限制基于以上知识点,假如我们已知一个IP地址,我们怎么才能获取解析到该IP地址的所有域名信息呢?一种方式是国家工信部能开放查询接口以供查询(不知道会不会开放?);另外一种方式就是接下来我要分享的——爬虫实现:根据IP地址反查域名。实现原理实现原理其实很简单,现在已有网站提供了根据IP地址查询域名的功能,但是需要人为登录网站输入IP地址查询,我想要实现程序自动化查询,所以就想到了爬虫的方式,简单来说,就是模拟人的查询行为,将查询结果解析成我想要的域名列表。以site.ip138.com为例,打开F12,输入一个IP查询,观察控制台请求,看到下图中信息请求地址为:http://site.ip138.com/119.75….请求方式为:GET然后,分析Response,可以看到,在页面上看到的绑定域名信息就是下图红框中<span>的内容,所以只要能将Response的内容解析出来,获取到<span>的内容就可以得到想要的域名列表。上述Response是HTML页面,使用jsoup来解析HTML简直完美。jsoup是什么?jsoup 是一款Java 的HTML解析器,可直接解析某个URL地址、HTML文本内容。它提供了一套非常省力的API,可通过DOM,CSS以及类似于jQuery的操作方法来取出和操作数据。//解析成Document对象Document document = Jsoup.parse(result);if (document == null) { logger.error(“Jsoup parse get document null!”);}//根据ID属性“list”获取元素Element对象(有没有感觉很像jQuery?)Element listEle = document.getElementById(“list”);//根据class属性和属性值筛选元素Element集合,并通过eachText()遍历元素内容return listEle.getElementsByAttributeValue(“target”, “blank”).eachText();result的内容通过HttpClient模拟HTTP请求HttpGet httpGet = new HttpGet(url);httpGet.setHeader(“Accept”, “text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,/;q=0.8”);httpGet.setHeader(“Accept-Encoding”, “gzip, deflate”);httpGet.setHeader(“Accept-Language”, “zh-CN,zh;q=0.9”);httpGet.setHeader(“Cache-Control”, “max-age=0”);httpGet.setHeader(“Connection”, “keep-alive”);httpGet.setHeader(“Cookie”, “Hm_lvt_d39191a0b09bb1eb023933edaa468cd5=1553090128; BAIDU_SSP_lcr=https://www.baidu.com/link?url=FS0ccst469D77DpdXpcGyJhf7OSTLTyk6VcMEHxT_9&wd=&eqid=fa0e26f70002e7dd000000065c924649; pgv_pvi=6200530944; pgv_si=s4712839168; Hm_lpvt_d39191a0b09bb1eb023933edaa468cd5=1553093270”);httpGet.setHeader(“DNT”, “1”);httpGet.setHeader(“Host”, host);httpGet.setHeader(“Upgrade-Insecure-Requests”, “1”);httpGet.setHeader(“User-Agent”, “Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36”);String result = HttpUtils.doGet(httpGet);HTTP请求工具类public class HttpUtils { private static Logger logger = LoggerFactory.getLogger(HttpUtils.class); public static String doGet(HttpGet httpGet) { CloseableHttpClient httpClient = null; try { httpClient = HttpClients.createDefault(); RequestConfig requestConfig = RequestConfig.custom() .setConnectTimeout(5000).setConnectionRequestTimeout(10000) .setSocketTimeout(5000).build(); httpGet.setConfig(requestConfig); HttpResponse httpResponse = httpClient.execute(httpGet); if (httpResponse.getStatusLine().getStatusCode() == 200 || httpResponse.getStatusLine().getStatusCode() == 302) { HttpEntity entity = httpResponse.getEntity(); return EntityUtils.toString(entity, “utf-8”); } else { logger.error(“Request StatusCode={}”, httpResponse.getStatusLine().getStatusCode()); } } catch (Exception e) { logger.error(“Request Exception={}:”, e); } finally { if (httpClient != null) { try { httpClient.close(); } catch (IOException e) { logger.error(“关闭httpClient失败”, e); } } } return null; }}新增Controller@RestControllerpublic class DomainSpiderController { private static Logger logger = LoggerFactory.getLogger(DomainSpiderController.class); @Autowired private DomainSpiderService domainSpiderService; /** * @param ip 119.75.217.109 * @return */ @RequestMapping("/spider/{ip}") @ResponseBody public List<String> domainSpider(@PathVariable(“ip”) String ip) { long startTime = System.currentTimeMillis(); List<String> domains = domainSpiderService.domainSpiderOfIp138(ip); if(domains == null || domains.size() == 0) { domains = domainSpiderService.domainSpiderOfAizan(ip); } long endTime = System.currentTimeMillis(); logger.info(“完成爬虫任务总耗时:{}s”, (endTime - startTime) / 1000); return domains; }}启动Spring Boot应用,访问浏览器:http://localhost:8080/spider/119.75.217.109获得返回结果如下:怎么样?是不是很简单?优化改进:有时候仅仅通过一个网站查询的域名数据可能不太准确,甚至查询不到数据,我们也没法判断谁才是正确的,所以,可以通过爬取多个网站的结果结合起来使用,例如:dns.aizhan.com提出疑问:这些提供根据IP反查域名的网站,是怎么实现的呢?我咨询过其他人,他们的回答是这些网站收集了很多IP和域名的对应关系,真实情况是这样的吗?示例源码domain-spider代码已上传至码云和Github上,欢迎下载学习GiteeGithub ...

April 13, 2019 · 1 min · jiezi

【CTF】广度搜索的 BeautifulSoup 网站爬虫

本人习惯使用pyhton2进行编程,因此beautifulsoup也是使用python2版本的,但据说python2明年就要停止支持了,忧伤得很。。。0x01 题目要求如图所示,在页面源码当中找到5个flag,然后拼接起来,还给了flagA的示例。flagA:打开站点是一个ctf-wiki的demo站点,了解这个站的人应该都知道它的体量,所以手动一个个找是不现实的,需要用到爬虫了(题目名称也暗示了)。0x02 解题思路我考虑使用广度优先搜索(BFS)实现一个网站爬虫,不了解广度搜索的童鞋可以自行百度。具体实现方法如下:建立待请求链接visiting_urls和已请求链接visited_urls的2个列表(也可看做队列)从visiting_urls取出一条链接,使用requrests.get请求页面源码在源码中正则匹配flag字段beautifulsoup获取页面中所有的a标签,符合要求的加入visiting_urlsvisiting_urls不为空,则执行[2]当中需要考虑2个问题:去重问题:当爬取链接时,难免会遇到存在不同位置的url指向同一个页面,爬取时不需要再请求相同页面,因此要对爬取到的url进行去重。方法如下:维护visiting_urls visited_urls列表,比对爬取url与已爬取过的url是否重复;根据mkdocs网站url特点,包含"../“的是回溯链接,此类链接不需要再次请求。正则匹配问题:这个方面没有多想,写个能使用的正则匹配规则就行,在本题中需要2种正则匹配:匹配flag:flag[ABCDE],我的目的是匹配到flag的标志,而不是把flag整个都匹配出来,因为我不清楚flag当中有没有其他奇怪字符,防止出现漏匹配的情况;匹配url:[\w/]+index.html,目的是匹配路径为字母数字(不包含”..")且末尾是"index.html"的url。到此,整个任务就完成了。0x03 完整脚本#coding=utf-8import requests,refrom bs4 import BeautifulSoups = requests.session()s.keep_alive=Falseflagre = re.compile(‘flag[ABCDE]’)urlre = re.compile(’[\w/]+index.html’)base_url = ‘http://23.236.125.55:1000/ctf-wiki/‘flagA_url = ‘http://23.236.125.55:1000/ctf-wiki/assembly/mips/readme/index.html’visiting_urls = [‘http://23.236.125.55:1000/ctf-wiki/index.html’]visited_urls = []def find_flag(url,html): flist = flagre.findall(html) if len(flist) > 0: print flist,urldef BFS(): url = visiting_urls[0] del(visiting_urls[0]) visited_urls.append(url) r = s.get(url) #r.encoding = ‘utf-8’ find_flag(url,r.text) soup = BeautifulSoup(r.text,’lxml’) for a in soup.find_all(‘a’): link = a[‘href’] if urlre.findall(link) and “..” not in link: new_url = base_url + link if new_url not in visited_urls and new_url not in visiting_urls: visiting_urls.append(new_url)if name == ‘main’: while len(visiting_urls) > 0: BFS()上面思路已经提到了,该脚本只能提取到包含flag标志的页面,而不是flag本身,因此还需要手动访问这些页面去寻找flag(手动狗头),如果还想直接显示flag,那就需要优化一下正则匹配了。提示一点,在获取到页面源码后,使用r.encoding = ‘utf-8’转码会导致EOFError,具体原因不详,本想能够匹配中文页面,结果画蛇添足搞了半天以为匹配没成功。提示两点,requests.session()的好处,相较于直接requests.get(),可以防止建立过多的HTTP连接,导致新连接无法建立的问题。参考页面:https://segmentfault.com/q/10…执行效果如下:最后拼接一下,完事了。 ...

April 7, 2019 · 1 min · jiezi

如何免费创建云端爬虫集群

移步 GitHub

April 4, 2019 · 1 min · jiezi

注入eval, Function等系统函数,截获动态代码

现在很多网站都上了各种前端反爬手段,无论手段如何,最重要的是要把包含反爬手段的前端javascript代码加密隐藏起来,然后在运行时实时解密动态执行。动态执行js代码无非两种方法,即eval和Function。那么,不管网站加密代码写的多牛,我们只要将这两个方法hook住,即可获取到解密后的可执行js代码。注意,有些网站会检测eval和Function这两个方法是否原生,因此需要一些小花招来忽悠过去。首先是eval的挂钩代码:(function() { if (window.__cr_eval) return window.__cr_eval = window.eval var myeval = function (src) { console.log("================ eval begin: length=" + src.length + “,caller=” + (myeval.caller && myeval.caller.name) + " ===============") console.log(src); console.log("================ eval end ================") return window.__cr_eval(src) } var _myeval = myeval.bind(null) _myeval.toString = window.__cr_eval.toString Object.defineProperty(window, ’eval’, { value: _myeval }) console.log(">>>>>>>>>>>>>> eval injected: " + document.location + " <<<<<<<<<<<<<<<<<<<")})();这段代码执行后,之后所有的eval操作都会在控制台打印输出将要执行的js源码。同理可以写出Function的挂钩代码:(function() { if (window.__cr_fun) return window.__cr_fun = window.Function var myfun = function () { var args = Array.prototype.slice.call(arguments, 0, -1).join(","), src = arguments[arguments.length - 1] console.log("================ Function begin: args=" + args + “, length=” + src.length + “,caller=” + (myfun.caller && myfun.caller.name) + " ===============") console.log(src); console.log("================ Function end ================") return window.__cr_fun.apply(this, arguments) } myfun.toString = function() { return window.__cr_fun + "" } Object.defineProperty(window, ‘Function’, { value: myfun }) console.log(">>>>>>>>>>>>>> Function injected: " + document.location + " <<<<<<<<<<<<<<<<<<<")})();注意和eval不同,Function是个变长参数的构造方法,需要处理this另外,有些网站还会用类似的机制加密页面内容,然后通过document.write输出动态解密的内容,因此同样可以挂钩document.write,挂钩方法类似eval,这里就不重复了。另外,还有个问题需要关注,就是挂钩代码的注入方法。最简单的就是F12调出控制台,直接执行上面的代码,但这样只能hook住之后的调用,如果希望从页面刚加载时就注入,那么可以用以下几种方式:油猴注入,油猴可以监听文档加载的几种不同状态,并在特定时刻执行js代码。我没有太多研究,具体请参见油猴手册代理注入,修改应答数据,在<head>标签内的第一个位置插入<script>节点,确保在其它js加载执行前注入;Fiddler, anyproxy等都可以编写外部规则,具体请参见代理工具的手册使用chrome-devtools-protocol, 通过Page.addScriptToEvaluateOnNewDocument注入外部js代码 ...

April 2, 2019 · 1 min · jiezi

国家企业信用公示系统的爬取

国家企业信用公示系统的爬取1. 网站分析1.1 获取首页通过 requests.get 直接请求网站首页,返回 521 错误提示码,返回结果是js代码。这是采用乐加速乐反爬技术,在访问前先判断客户端的cookie是否正确,如果不正确,返回521状态码和一段js代码,并且进行set-cookie操作,返回的js代码经过浏览器执行又会生成新的cookie,这两个cookie一起发送给服务器,才会返回正确的网页内容。解决方法: 通过python的 execjs 执行返回的js,拿到新的cookie,和第一次请求的cookie一起发送给服务器。具体操作:response = self.session.get(self.cookie_url)js_code1 = response.textprint(js_code1)print(response.cookies)输出<script>var x=“toLowerCase@@@@267@@@@window@36@@@@firstChild@div@@catch@@var@rOm9XFMtA3QKV7nYsPGT4lifyWwkq5vcjH2IdxUoCbhERLaz81DNB6@@dpUWKvmM@@substr@@f@53@30@@split@@String@e@location@cookie@g@if@3D@@1@@for@@RegExp@1500@0@@try@pathname@@@@@length@@@@@reverse@1553939630@DOMContentLoaded@@Path@@19@eval@@https@parseInt@chars@innerHTML@charAt@return@2@while@@match@attachEvent@@challenge@@@Sat@@search@10@charCodeAt@createElement@hgCxEv@JgSe0upZ@__jsl_clearance@Array@addEventListener@50@@2FP@setTimeout@a@false@fromCharCode@8@Mar@@0xEDB88320@href@@captcha@replace@function@0xFF@@new@@@d@onreadystatechange@document@@join@@GMT@@toString@Expires@W7@else@@BWEUk”.replace(/@$/,"").split("@"),y=“j 3=34(){2r(‘y.30=y.1e+y.2f.33(/[\?|&]32-2a/,\’\’)’,1a);3c.10=‘2l=1p.5|1b|’+(34(){j 3=2m(+[[[-~{}+(+!-[])+(+!-[])](-{}+(+!-[])+(+!-[]))]]),1g=[’%’,[[(+!-[])+(+!-[])]/~~’’+[[]][1b]][1b].22(24),‘3n%2q’,[{}+[]+[]][1b].22(-~~~’’-[-~~~’’+(-~~~’’<<-~~~’’)+((+!-[])+(+!-[])^-[])]),‘3k’,(!{}+[]+[[]][1b]).22(-!{})+[[(+!-[])+(+!-[])]/’’+[[]][1b]][1b].22(24),‘2j’,((-~~~’’<<-~~~’’)+[[]][1b]),’m%13’];17(j 1n=1b;1n<1g.1j;1n++){3.1o()[1n]=1g[1n]};23 3.3e(’’)})()+’;3j=2d, s-2w-1u 2g:r:2o 3g;1s=/;’};12((34(){1d{23 !!9.2n;}h(x){23 2t;}})()){3c.2n(‘1q’,3,2t)}3l{3c.28(‘3b’,3)}",f=function(x,y){var a=0,b=0,c=0;x=x.split(”");y=y||99;while((a=x.shift())&&(b=a.charCodeAt(0)-77.5))c=(Math.abs(b)<13?(b+48.5):parseInt(a,36))+yc;return c},z=f(y.match(/\w/g).sort(function(x,y){return f(x)-f(y)}).pop());while(z++)try{eval(y.replace(/\b\w+\b/g, function(y){return x[f(y,z)-1]||(""+y)}));break}catch(){}</script><RequestsCookieJar[<Cookie __jsluid=30d33a1b087a857d4fd13e63223f63c9 for www.gsxt.gov.cn/>]>对这段代码进行js反混淆,得到<script> var x = “toLowerCase@@@@267@@@@window@36@@@@firstChild@div@@catch@@var@rOm9XFMtA3QKV7nYsPGT4lifyWwkq5vcjH2IdxUoCbhERLaz81DNB6@@dpUWKvmM@@substr@@f@53@30@@split@@String@e@location@cookie@g@if@3D@@1@@for@@RegExp@1500@0@@try@pathname@@@@@length@@@@@reverse@1553939630@DOMContentLoaded@@Path@@19@eval@@https@parseInt@chars@innerHTML@charAt@return@2@while@@match@attachEvent@@challenge@@@Sat@@search@10@charCodeAt@createElement@hgCxEv@JgSe0upZ@__jsl_clearance@Array@addEventListener@50@@2FP@setTimeout@a@false@fromCharCode@8@Mar@@0xEDB88320@href@@captcha@replace@function@0xFF@@new@@@d@onreadystatechange@document@@join@@GMT@@toString@Expires@W7@else@@BWEUk”.replace(/@$/, “”).split("@"), y = “j 3=34(){2r(‘y.30=y.1e+y.2f.33(/[\?|&]32-2a/,\’\’)’,1a);3c.10=‘2l=1p.5|1b|’+(34(){j 3=2m(+[[[-{}+(+!-[])+(+!-[])]*(-{}+(+!-[])+(+!-[]))]]),1g=[’%’,[[(+!-[])+(+!-[])]/’’+[[]][1b]][1b].22(24),‘3n%2q’,[{}+[]+[]][1b].22(-~~~’’-[-~~~’’+(-~~~’’<<-~~~’’)+((+!-[])+(+!-[])^-[])]),‘3k’,(!{}+[]+[[]][1b]).22(-!{})+[[(+!-[])+(+!-[])]/~~’’+[[]][1b]][1b].22(24),‘2j’,((-~~~’’<<-~~~’’)+[[]][1b]),’m%13’];17(j 1n=1b;1n<1g.1j;1n++){3.1o()[1n]=1g[1n]};23 3.3e(’’)})()+’;3j=2d, s-2w-1u 2g:r:2o 3g;1s=/;’};12((34(){1d{23 !!9.2n;}h(x){23 2t;}})()){3c.2n(‘1q’,3,2t)}3l{3c.28(‘3b’,3)}”, f = function(x, y) { var a = 0, b = 0, c = 0; x = x.split(""); y = y || 99; while ((a = x.shift()) && (b = a.charCodeAt(0) - 77.5)) c = (Math.abs(b) < 13 ? (b + 48.5) : parseInt(a, 36)) + y * c; return c }, z = f(y.match(/\w/g).sort(function(x, y) { return f(x) - f(y) }).pop()); while (z++) try { eval(y.replace(/\b\w+\b/g, function(y) { return x[f(y, z) - 1] || ("" + y) })); break } catch () {}</script>对js代码进行格式化处理,再调用python的execjs执行这段代码,如下所示:js_code1 = js_code1.rstrip(’\n’)js_code1 = js_code1.replace(’</script>’, ‘’)js_code1 = js_code1.replace(’<script>’, ‘’)index = js_code1.rfind(’}’)js_code1 = js_code1[0:index + 1]js_code1 = ‘function getCookie() {’ + js_code1 + ‘}‘js_code1 = js_code1.replace(’eval’, ‘return’)js_code2 = execjs.compile(js_code1) code = js_code2.call(‘getCookie’)print(code)得到结果为:var _1l=function(){setTimeout(’location.href=location.pathname+location.search.replace(/[?|&]captcha-challenge/,'')’,1500);document.cookie=’__jsl_clearance=1553940235.414|0|’+(function(){var _1l=Array(+[[-!{}]+[-!{}]]),_O=[‘URpBd’,(((+!-[])+[(+!-[])+(+!-[])]>>(+!-[])+(+!-[]))+[[]][0]),‘BM’,(((+!-[])+[(+!-[])+(+!-[])]>>(+!-[])+(+!-[]))+[[]][0])+[{}+[]+[[]][0]][0].charAt(-{}),‘GQ7RqBB’,(!+[]+[]+[[]][0]).charAt(’’)+[[(+!-[])+(+!-[])]/’’+[]][0].charAt((-~~~’’<<-~~~’’)+([(+!-[])+(+!-[])]+!{}>>(+!-[])+(+!-[]))),‘LMV’,({}+[]).charAt([-~!{}]+(’’+[[]][0])),‘BUc%’,(!{}+[]+[[]][0]).charAt(-!{})+[{}+[]+[[]][0]][0].charAt(-{}),‘D’];for(var _7=0;_7<_O.length;_7++){_1l.reverse()[_7]=_O[_7]};return _1l.join(’’)})()+’;Expires=Sat, 30-Mar-19 11:03:55 GMT;Path=/;’};if((function(){try{return !!window.addEventListener;}catch(e){return false;}})()){document.addEventListener(‘DOMContentLoaded’,_1l,false)}else{document.attachEvent(‘onreadystatechange’,_1l)}同样进行js反混淆,得到var _1l = function () { setTimeout(’location.href=location.pathname+location.search.replace(/[?|&]captcha-challenge/,'')’, 1500); document.cookie = ‘__jsl_clearance=1553940235.414|0|’ + (function () { var _1l = Array(+[ [-!{}] + [-!{}] ]), _O = [‘URpBd’, (((+!-[]) + [(+!-[]) + (+!-[])] >> (+!-[]) + (+!-[])) + [ [] ][0]), ‘BM’, (((+!-[]) + [(+!-[]) + (+!-[])] >> (+!-[]) + (+!-[])) + [ [] ][0]) + [{} + [] + [ [] ][0]][0].charAt(-~ {}), ‘GQ7RqBB’, (!+[] + [] + [ [] ][0]).charAt(~~’’) + [ [(+!-[]) + (+!-[])] / ~~’’ + [] ][0].charAt((-~~~’’ << -~~~’’) + ([(+!-[]) + (+!-[])] + !{} >> (+!-[]) + (+!-[]))), ‘LMV’, ({} + []).charAt([-~!{}] + (’’ + [ [] ][0])), ‘BUc%’, (!{} + [] + [ [] ][0]).charAt(-!{}) + [{} + [] + [ [] ][0]][0].charAt(- {}), ‘D’]; for (var _7 = 0; _7 < _O.length; _7++) { _1l.reverse()[_7] = _O[_7] }; return _1l.join(’’) })() + ‘;Expires=Sat, 30-Mar-19 11:03:55 GMT;Path=/;’};if ((function () {try { return !!window.addEventListener;} catch (e) { return false;}})()) { document.addEventListener(‘DOMContentLoaded’, _1l, false)} else { document.attachEvent(‘onreadystatechange’, _1l)}同样对这段js代码格式化,再用python的execjs进行调用,得到结果code = ‘var a’ + code.split(‘document.cookie’)[1].split(“Path=/;’”)[0] + “Path=/;’;return a;“code = ‘window = {}; \n’ + codejs_final = “function getClearance(){” + code + “};“ctx = execjs.compile(js_final)jsl_clearance = ctx.call(‘getClearance’)jsl_cle = jsl_clearance.split(’;’)[0].split(’=’)[1]print(’__jsl_clearance=’ + jsl_cle)得到最终生成的cookie__jsl_clearance=1553940235.414|0|URpBdaoBMjGQ7RqBBtyLMV3oBUc%3D与第一次请求得到的cookie 一起发送给服务器,就能返回正常结果。1.2 验证码破解此网站采用的是极验验证码,调用打码平台进行验证码破解。验证码识别开发者文档代码num = int(time.time() * 1000)url = ‘http://www.gsxt.gov.cn/SearchItemCaptcha?t={}'.format(num)response = request.get(url)print(response.text)json_data = response.textdict_data = json.loads(json_data)url = ‘http://jiyanapi.c2567.com/shibie?gt={}&challenge={}&referer=http://www.gsxt.gov.cn&user={}&pass={}&return=json'.format(dict_data['gt'], dict_data[‘challenge’], username, password)response = self.session.get(url)json_data = response.textprint(json_data)结果:{“status”:“ok”,“challenge”:“4cab66342cb0def73b5b78e5337d51b2”,“validate”:“2686d56f9c350c4dd4c90b762c3afa7a”}1.3 查询企业post_params = { ‘geetest_challenge’: data.get(‘challenge’), ‘geetest_validate’: data.get(‘validate’), ‘geetest_seccode’: data.get(‘validate’) + ‘|jordan’, ’tab’: ’ent_tab’, ‘province’: ‘’, # token: 在首页的源代码中,有一句注释:#TODO 伪造极验变量 ’token’: ‘2016’, ‘searchword’: ‘百度’}response = self.session.post(self.post_url, data=post_params)print(response.text)return response.text成功获取到结果! ...

March 30, 2019 · 2 min · jiezi

多线程+代理池爬取天天基金网、股票数据(无需使用爬虫框架)

@[TOC]简介提到爬虫,大部分人都会想到使用Scrapy工具,但是仅仅停留在会使用的阶段。为了增加对爬虫机制的理解,我们可以手动实现多线程的爬虫过程,同时,引入IP代理池进行基本的反爬操作。本次使用天天基金网进行爬虫,该网站具有反爬机制,同时数量足够大,多线程效果较为明显。技术路线IP代理池多线程爬虫与反爬编写思路首先,开始分析天天基金网的一些数据。经过抓包分析,可知:./fundcode_search.js包含所有基金的数据,同时,该地址具有反爬机制,多次访问将会失败的情况。同时,经过分析可知某只基金的相关信息地址为:fundgz.1234567.com.cn/js/ + 基金代码 + .js分析完天天基金网的数据后,搭建IP代理池,用于反爬作用。点击这里搭建代理池,由于该作者提供了一个例子,所以本代码里面直接使用的是作者提供的接口。如果你需要更快速的获取到普匿IP,则可以自行搭建一个本地IP代理池。 # 返回一个可用代理,格式为ip:端口 # 该接口直接调用github代理池项目给的例子,故不保证该接口实时可用 # 建议自己搭建一个本地代理池,这样获取代理的速度更快 # 代理池搭建github地址https://github.com/1again/ProxyPool # 搭建完毕后,把下方的proxy.1again.cc改成你的your_server_ip,本地搭建的话可以写成127.0.0.1或者localhost def get_proxy(): data_json = requests.get(“http://proxy.1again.cc:35050/api/v1/proxy/?type=2").text data = json.loads(data_json) return data[‘data’][‘proxy’]搭建完IP代理池后,我们开始着手多线程爬取数据的工作。一旦使用多线程,则需要考虑到数据的读写顺序问题。这里使用python中的队列queue进行存储基金代码,不同线程分别从这个queue中获取基金代码,并访问指定基金的数据。由于queue的读取和写入是阻塞的,所以可以确保该过程不会出现读取重复和读取丢失基金代码的情况。 # 将所有基金代码放入先进先出FIFO队列中 # 队列的写入和读取都是阻塞的,故在多线程情况下不会乱 # 在不使用框架的前提下,引入多线程,提高爬取效率 # 创建一个队列 fund_code_queue = queue.Queue(len(fund_code_list)) # 写入基金代码数据到队列 for i in range(len(fund_code_list)): #fund_code_list[i]也是list类型,其中该list中的第0个元素存放基金代码 fund_code_queue.put(fund_code_list[i][0])现在,开始编写如何获取指定基金的代码。首先,该函数必须先判断queue是否为空,当不为空的时候才可进行获取基金数据。同时,当发现访问失败时,则必须将我们刚刚取出的基金代码重新放回到队列中去,这样才不会导致基金代码丢失。 # 获取基金数据 def get_fund_data(): # 当队列不为空时 while (not fund_code_queue.empty()): # 从队列读取一个基金代码 # 读取是阻塞操作 fund_code = fund_code_queue.get() # 获取一个代理,格式为ip:端口 proxy = get_proxy() # 获取一个随机user_agent和Referer header = {‘User-Agent’: random.choice(user_agent_list), ‘Referer’: random.choice(referer_list) } try: req = requests.get(“http://fundgz.1234567.com.cn/js/" + str(fund_code) + “.js”, proxies={“http”: proxy}, timeout=3, headers=header) except Exception: # 访问失败了,所以要把我们刚才取出的数据再放回去队列中 fund_code_queue.put(fund_code) print(“访问失败,尝试使用其他代理访问”)当访问成功时,则说明能够成功获得基金的相关数据。当我们在将这些数据存入到一个.csv文件中,会发现数据出现错误。这是由于多线程导致,由于多个线程同时对该文件进行写入,导致出错。所以需要引入一个线程锁,确保每次只有一个线程写入。 # 申请获取锁,此过程为阻塞等待状态,直到获取锁完毕 mutex_lock.acquire() # 追加数据写入csv文件,若文件不存在则自动创建 with open(’./fund_data.csv’, ‘a+’, encoding=‘utf-8’) as csv_file: csv_writer = csv.writer(csv_file) data_list = [x for x in data_dict.values()] csv_writer.writerow(data_list) # 释放锁 mutex_lock.release()至此,大部分工作已经完成了。为了更好地实现伪装效果,我们对header进行随机选择。 # user_agent列表 user_agent_list = [ ‘Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.71 Safari/537.1 LBBROWSER’, ‘Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; QQDownload 732; .NET4.0C; .NET4.0E)’, ‘Mozilla/5.0 (Windows NT 5.1) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.84 Safari/535.11 SE 2.X MetaSr 1.0’, ‘Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Maxthon/4.4.3.4000 Chrome/30.0.1599.101 Safari/537.36’, ‘Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.122 UBrowser/4.0.3214.0 Safari/537.36’ ] # referer列表 referer_list = [ ‘http://fund.eastmoney.com/110022.html’, ‘http://fund.eastmoney.com/110023.html’, ‘http://fund.eastmoney.com/110024.html’, ‘http://fund.eastmoney.com/110025.html’ ] # 获取一个随机user_agent和Referer header = {‘User-Agent’: random.choice(user_agent_list), ‘Referer’: random.choice(referer_list) }最后,在main中,开启线程即可。 # 创建一个线程锁,防止多线程写入文件时发生错乱 mutex_lock = threading.Lock() # 线程数为50,在一定范围内,线程数越多,速度越快 for i in range(50): t = threading.Thread(target=get_fund_data,name=‘LoopThread’+str(i)) t.start()通过对多线程和IP代理池的实践操作,能够更加深入了解多线程和爬虫的工作原理。当你在使用一些爬虫框架的时候,就能够做到快速定位错误并解决错误。数据格式000056,建信消费升级混合,2019-03-26,1.7740,1.7914,0.98,2019-03-27 15:00000031,华夏复兴混合,2019-03-26,1.5650,1.5709,0.38,2019-03-27 15:00000048,华夏双债增强债券C,2019-03-26,1.2230,1.2236,0.05,2019-03-27 15:00000008,嘉实中证500ETF联接A,2019-03-26,1.4417,1.4552,0.93,2019-03-27 15:00000024,大摩双利增强债券A,2019-03-26,1.1670,1.1674,0.04,2019-03-27 15:00000054,鹏华双债增利债券,2019-03-26,1.1697,1.1693,-0.03,2019-03-27 15:00000016,华夏纯债债券C,2019-03-26,1.1790,1.1793,0.03,2019-03-27 15:00功能截图配置说明# 确保安装以下库,如果没有,请在python3环境下执行pip install 模块名 import requests import random import re import queue import threading import csv import json补充完整版源代码存放在github上,有需要的可以下载项目持续更新,欢迎您star本项目 ...

March 27, 2019 · 2 min · jiezi

基于Python的模拟登陆获取脉脉好友信息

代码已经上传到github上简介:这是一个基于python3而写的爬虫,爬取的网站的脉脉网(https://maimai.cn/),在搜索框中搜索“CHO”,并切换到“人脉”选项卡,点击姓名,进入详情页,爬取其详细信息获取的具体信息有:基本信息、工作经历、教育经历、职业标签及其认可数、点评信息几度关系:一度、二度、三度等写给用户的注意:如果你只是想使用这个项目,那么你可以看这里如何使用:使用之前,你要已经保证安装好相关的库和软件:rerequestsseleniumloggingpymysqlchromemysql使用:从github上复制代码填写自己的脉脉手机号和密码(你可以在login.py文件中找到他)建表(详细建表见下)运行程序login.py详细建表需要5张表,下面附上代码:表1:basic_info(脉脉好友基本信息)CREATE TABLE basic_info ( id int(11) NOT NULL AUTO_INCREMENT COMMENT ‘ID’, name varchar(56) NOT NULL COMMENT ‘名字’, mmid int(11) NOT NULL COMMENT ‘mmid’, rank int(11) DEFAULT NULL COMMENT ‘影响力’, company varchar(128) DEFAULT NULL COMMENT ‘目前公司简称’, stdname varchar(128) DEFAULT NULL COMMENT ‘目前公司全称’, position varchar(128) DEFAULT NULL COMMENT ‘目前职位’, headline text COMMENT ‘自我介绍’, ht_province varchar(128) DEFAULT NULL COMMENT ‘家乡-省’, ht_city varchar(128) DEFAULT NULL COMMENT ‘家乡-城市’, email varchar(128) DEFAULT NULL COMMENT ‘邮箱’, mobile varchar(128) DEFAULT NULL COMMENT ‘手机’, dist tinyint(1) DEFAULT NULL COMMENT ‘几度关系’, PRIMARY KEY (id), UNIQUE KEY mmid (mmid)) ENGINE=InnoDB AUTO_INCREMENT=873 DEFAULT CHARSET=gbk ROW_FORMAT=DYNAMIC COMMENT=‘脉脉好友基本信息’]表2:education_exp(脉脉好友教育经历)CREATE TABLE education_exp ( id int(11) NOT NULL AUTO_INCREMENT COMMENT ‘ID’, mmid int(11) NOT NULL COMMENT ‘mmid’, school_name varchar(128) DEFAULT NULL COMMENT ‘学校名称’, department varchar(128) DEFAULT NULL COMMENT ‘专业’, education int(5) DEFAULT NULL COMMENT ‘学历(0:专科,1:本科,2:硕士,3:博士,255:其他)’, start_year int(11) DEFAULT NULL COMMENT ‘开始时间(年)默认为0000’, start_mon int(11) DEFAULT NULL COMMENT ‘开始时间(月)默认为0000’, end_year int(11) DEFAULT NULL COMMENT ‘结束时间(年)默认为0000’, end_mon int(11) DEFAULT NULL COMMENT ‘结束时间(月)默认为0000’, PRIMARY KEY (id), UNIQUE KEY mmid (mmid,school_name,education,start_year)) ENGINE=InnoDB AUTO_INCREMENT=1064 DEFAULT CHARSET=gbk ROW_FORMAT=DYNAMIC COMMENT=‘脉脉好友教育经历’表3:review_info(脉脉好友点评信息)CREATE TABLE review_info ( id int(11) NOT NULL AUTO_INCREMENT COMMENT ‘ID’, mmid int(11) NOT NULL COMMENT ‘mmid’, reviewer varchar(128) DEFAULT NULL COMMENT ‘点评人’, relationship varchar(128) DEFAULT NULL COMMENT ‘关系’, position varchar(128) DEFAULT NULL COMMENT ‘点评人职位’, eva_info text COMMENT ‘评价信息’, PRIMARY KEY (id), UNIQUE KEY mmid (mmid,reviewer)) ENGINE=InnoDB AUTO_INCREMENT=400 DEFAULT CHARSET=gbk ROW_FORMAT=DYNAMIC COMMENT=‘脉脉好友点评信息’表4:tag_info(脉脉好友点评信息)CREATE TABLE tag_info ( id int(11) NOT NULL AUTO_INCREMENT COMMENT ‘ID’, mmid int(11) NOT NULL COMMENT ‘mmid’, tag varchar(128) DEFAULT NULL COMMENT ‘标签’, rec_num varchar(128) DEFAULT NULL COMMENT ‘认可度’, PRIMARY KEY (id), UNIQUE KEY UNIQUE (mmid,tag,rec_num)) ENGINE=InnoDB AUTO_INCREMENT=5881 DEFAULT CHARSET=gbk ROW_FORMAT=DYNAMIC COMMENT=‘脉脉好友点评信息’表5:work_exp(脉脉好友工作经历)CREATE TABLE work_exp ( id int(11) NOT NULL AUTO_INCREMENT COMMENT ‘ID’, mmid int(11) NOT NULL COMMENT ‘mmid’, company varchar(128) DEFAULT NULL COMMENT ‘公司简称’, stdname varchar(128) DEFAULT NULL COMMENT ‘公司全称’, et_url varchar(244) DEFAULT NULL COMMENT ‘公司页面url’, description text COMMENT ‘描述’, start_year int(11) DEFAULT NULL COMMENT ‘开始时间(年)默认0000’, start_mon int(11) DEFAULT NULL COMMENT ‘开始时间(月)默认0000’, end_year int(11) DEFAULT NULL COMMENT ‘结束时间(年)默认0000’, end_mon int(11) DEFAULT NULL COMMENT ‘结束时间(月)默认0000’, position varchar(128) DEFAULT NULL COMMENT ‘职位’, PRIMARY KEY (id), UNIQUE KEY UNIQUE (mmid,company,start_year,position)) ENGINE=InnoDB AUTO_INCREMENT=2582 DEFAULT CHARSET=gbk ROW_FORMAT=DYNAMIC COMMENT=‘脉脉好友工作经历’写给开发者的如果你是一个开发者,那么请看这里具体的每个函数的作用,就不细说了,代码中有注释,可以自行查看请求:在这里我们主要说说请求这一块:请求需要解决2个问题:第一个是使用cookies模拟登陆第二个是数据获取模拟登陆这是使用selenium驱动浏览器登录脉脉,然后获取cookie这样来就省去了自己去拼接cookie的麻烦,获取到cookie之后,在利用cookie来进行requests请求数据,这里不再使用selenium是因为selenium太慢,而且比较容易出错看一下代码,思路是:设置selenium参数使用selenium打开到登录网址输入手机号和密码,进行登录获取cookies,并存到json文件中这样就获取cookies成功了,之后将cookies添加到requests请求中,进行数据获取def get_cookies(): """ 使用selenium获取cookies的值,将其存在文件中 :return: """ logger.info(“使用selenium获取cookies”) chrome_options = Options() chrome_options.add_argument(’–no-sandbox’) # 解决DevToolsActivePort文件不存在的报错 chrome_options.add_argument(’–disable-gpu’) # 谷歌文件提到需要加这个属性来规避bug chrome_options.add_argument(’–headless’) # 无界面设置 chrome = webdriver.Chrome(chrome_options=chrome_options) login_url = “https://acc.maimai.cn/login" chrome.get(login_url) wait = WebDriverWait(chrome, 10) wait.until(EC.element_to_be_clickable((‘xpath’, “//input[@class=‘loginBtn’]”))) time.sleep(1) user_name = “” # 你的手机号 password = “” # 你的密码 chrome.find_element(“xpath”, “//input[@class=‘loginPhoneInput’]”).send_keys(user_name) time.sleep(1) chrome.find_element(“xpath”, “//input[@id=‘login_pw’]”).send_keys(password) chrome.find_element(‘xpath’, “//input[@class=‘loginBtn’]”).click() cookies = chrome.get_cookies() with open(“cookie.json”, “w+")as f: f.write(json.dumps(cookies)) f.close() logger.info(“cookies获取成功”) chrome.close()数据获取这里面主要有2个问题:第一个网站使用了ajax加载,我们需要找到我们需要的url第二个是获取的html需要进行处理,才能被我们所利用ajax加载直接去请求https://maimai.cn/web/search_center?type=contact&query=cho&highlight=true这里,获取的信息是不全的,这里使用了ajax加载,我们打开google看一下,按F12打开开发者模式看一下如图,我们可以在这里找我们需要的数据,这个是url:https://maimai.cn/search/cont…,这里获取的是json格式的数据,非常好解析,再进一步去获取详细信息的页面的时候,其方法也是一样的,这里就不多做说明了html页面数据处理的问题有一部分的数据是以json的形式返回给我们的,但是还有一些数据是在原网页中一起返回的,虽然也是json的形式,但是处理起来还是有一定的麻烦,看一下原网页的数据这个JSON.parse后面的数据也是我们需要的,可以发现这里有一些像"u0022"的数据,这个其实是双引号的utf8编码,还有一些其他的字符,会以utf8编码的形式返回的,直接处理很不好处理,我们先将这些字符编码替换为对应的字符,之后转成json的格式进行解析,这样就方便多了,看一下代码:def json_info(html): "”” 处理获取的html,转换为json格式 :param html: :return: """ print(html.text) c = re.search(‘JSON.parse("(.*?)");</script><script\ssrc=’, html.text, re.S).group(1) # 正则匹配所需要的数据 d = c.replace(’\u0022’, ‘"’).replace("\u002D", “-”).replace("\u0026", ‘&’).replace("\u005C", “\”) # 对数据进行处理 data = json.loads(d) return data思路是很简单的:先是将用正则匹配所需要的数据拿下来之后将一些utf8编码的字符替换掉转换为json的格式进行解析主要需要解决的问题就只有这些,其他的一些数据处理,存储都是很简单和常见的,就不多做说明了 ...

March 27, 2019 · 3 min · jiezi

基于Python爬取天眼查网站的企业信息

爬虫简介这是一个在未登录的情况下,根据企业名称搜索,爬取企业页面数据的采集程序注意: 这是一个比较简单的爬虫,基本上只用到了代理,没有用到其他的反反爬技术,不过由于爬取的数据比较多,适合刷解析技能的熟练度,所以高手勿进代码已经上传到GitHub上,有用还请给个星python版本:python2.7编码工具:pycharm数据存储:mysql爬虫结构:广度爬虫爬虫思路:先获取需要采集信息的公司:从数据库中获取获取字段:etid,etname将获取的数据存储的状态表中从状态表中获取数据,并更新状态表拼接初始URL:将etname和初始url进行拼接,获得初始网址将初始url放到一个列表中,获取HTML的时候如何出错,将出错的url放到另一个列表中,进行循环获取请求解析初始一级页面:验证查询的公司是否正确(??)获取二级页面url将二级url放到一个列表中,获取HTML的时候如何出错,将出错的url放到另一个列表中,进行循环获取请求解析二级页面:获取的信息待定将公司的信息存储到数据库中:建表存储信息所建的表:企业主要信息: et_host_info工商信息: et_busi_info分支机构信息: et_branch_office软件著作权信息: et_container_copyright_info网站备案信息: et_conrainer_icp_info对外投资信息: et_foreign_investment_info融资信息: et_rongzi_info股东信息: et_stareholder_info商标信息: et_trademark_info微信公众号信息:et_wechat_list_info状态表: et_name_status看一下部分的结果图:

March 26, 2019 · 1 min · jiezi

爬取5K分辨率超清唯美壁纸

@[toc]爬取5K分辨率超清唯美壁纸简介壁纸的选择其实很大程度上能看出电脑主人的内心世界,有的人喜欢风景,有的人喜欢星空,有的人喜欢美女,有的人喜欢动物。然而,终究有一天你已经产生审美疲劳了,但你下定决定要换壁纸的时候,又发现网上的壁纸要么分辨率低,要么带有水印。这里有一款Mac下的小清新壁纸神器Pap.er,可能是Mac下最好的壁纸软件,自带5K超清分辨率壁纸,富有多种类型壁纸,当我们想在Windows或者Linux下使用的时候,就可以考虑将5K超清分辨率壁纸爬取下来。编写思路为了方便快速开发,我们使用python中的wxpy模块完成微信的基本操作。首先,打开Charles软件,进行抓包。打开Pap.er,开始抓包。(由于是Mac系统下的APP,所以非Mac系统的朋友可以直接看抓包结果)抓包分析结果如下:类型地址最新https://service.paper.meiyuan…最热https://service.paper.meiyuan…女生https://service.paper.meiyuan…星空https://service.paper.meiyuan…参数page不用改动,per_page指的是每页提取的数量,也就是我们想要提取的图片数量。抓完包之后,我们开始编写5K壁纸解析程序# 爬取不同类型图片def crawler_photo(type_id, photo_count): # 最新 1, 最热 2, 女生 3, 星空 4 if(type_id == 1): url = ‘https://service.paper.meiyuan.in/api/v2/columns/flow/5c68ffb9463b7fbfe72b0db0?page=1&per_page=' + str(photo_count) elif(type_id == 2): url = ‘https://service.paper.meiyuan.in/api/v2/columns/flow/5c69251c9b1c011c41bb97be?page=1&per_page=' + str(photo_count) elif(type_id == 3): url = ‘https://service.paper.meiyuan.in/api/v2/columns/flow/5c81087e6aee28c541eefc26?page=1&per_page=' + str(photo_count) elif(type_id == 4): url = ‘https://service.paper.meiyuan.in/api/v2/columns/flow/5c81f64c96fad8fe211f5367?page=1&per_page=' + str(photo_count) headers = {“User-Agent”: “Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36”} # 获取图片链接列表数据,json格式 respond = requests.get(url, headers=headers) # 对json格式转化为python对象 photo_data = json.loads(respond.content) # 已经下载的图片张数 now_photo_count = 1 # 所有图片张数 all_photo_count = len(photo_data) # 开始下载并保存5K分辨率壁纸 for photo in photo_data: # 创建一个文件夹存放我们下载的图片(若存在则不用重新创建) if not os.path.exists(’./’ + str(type_id)): os.makedirs(’./’ + str(type_id)) # 准备下载的图片链接,5K超清壁纸链接 file_url = photo[‘urls’][‘raw’] # 准备下载的图片名称,不包含扩展名 file_name_only = file_url.split(’/’) file_name_only = file_name_only[len(file_name_only) -1] # 准备保存到本地的完整路径 file_full_name = ‘./’ + str(type_id) + ‘/’ + file_name_only # 开始下载图片 Down_load(file_url, file_full_name, now_photo_count, all_photo_count) # 已经下载的图片数量加1 now_photo_count = now_photo_count + 1根据不同类型的壁纸,创建不同的文件夹编号进行分类。上面的Down_load()函数是下载文件的意思,调用requests库,具体代码如下:# 文件下载器def Down_load(file_url, file_full_name, now_photo_count, all_photo_count): headers = {“User-Agent”: “Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36”} # 开始下载图片 with closing(requests.get(file_url, headers=headers, stream=True)) as response: chunk_size = 1024 # 单次请求最大值 content_size = int(response.headers[‘content-length’]) # 文件总大小 data_count = 0 # 当前已传输的大小 with open(file_full_name, “wb”) as file: for data in response.iter_content(chunk_size=chunk_size): file.write(data) done_block = int((data_count / content_size) * 50) data_count = data_count + len(data) now_jd = (data_count / content_size) * 100 print("\r %s:[%s%s] %d%% %d/%d" % (file_full_name, done_block * ‘█’, ’ ’ * (50 - 1 - done_block), now_jd, now_photo_count, all_photo_count), end=" “) # 下载完图片后获取图片扩展名,并为其增加扩展名 file_type = filetype.guess(file_full_name) os.rename(file_full_name, file_full_name + ‘.’ + file_type.extension)chunk_size指的是单次请求的最大值,content_size指的就是我们下载5K超清壁纸的大小,为了能够直观显示下载情况,所以添加了下载进度条的显示效果。核心代码为file.write(data)。下载完毕后,为了方便我们查看文件,所以需要给图片添加对应的扩展名,比如jpg,png,gif,这里使用到filetype库对文件进行解析,判断其类型。最后,开始在main中爬取5K高清壁纸:if name == ‘main’: # 最新 1, 最热 2, 女生 3, 星空 4 # 爬取类型为3的图片(女生),一共准备爬取100张 print(“程序已经开始运行,请稍等……”) crawler_photo(1, 100) crawler_photo(2, 100) crawler_photo(3, 100) crawler_photo(4, 100)使用教程确保以下库均已安装:# 如果没有安装,请使用pip install module安装import requestsimport filetypeimport osimport jsonfrom contextlib import closing演示图片完整源代码项目源代码在GitHub仓库项目持续更新,欢迎您star本项目 ...

March 16, 2019 · 2 min · jiezi

手把手教你如何用Crawlab构建技术文章聚合平台(一)

背景说到爬虫,大多数程序员想到的是scrapy这样受人欢迎的框架。scrapy的确不错,而且有很强大的生态圈,有gerapy等优秀的可视化界面。但是,它还是有一些不能做到的事情,例如在页面上做翻页点击操作、移动端抓取等等。对于这些新的需求,可以用Selenium、Puppeteer、Appium这些自动化测试框架绕开繁琐的动态内容,直接模拟用户操作进行抓取。可惜的是,这些框架不是专门的爬虫框架,不能对爬虫进行集中管理,因此对于一个多达数十个爬虫的大型项目来说有些棘手。Crawlab是一个基于Celery的分布式通用爬虫管理平台,擅长将不同编程语言编写的爬虫整合在一处,方便监控和管理。Crawlab有精美的可视化界面,能对多个爬虫进行运行和管理。任务调度引擎是本身支持分布式架构的Celery,因此Crawlab可以天然集成分布式爬虫。有一些朋友认为Crawlab只是一个任务调度引擎,其实这样认为并不完全正确。Crawlab是类似Gerapy这样的专注于爬虫的管理平台。本文将介绍如何使用Crawlab和Puppeteer抓取主流的技术博客文章,然后用Flask+Vue搭建一个小型的技术文章聚合平台。Crawlab在前一篇文章《分布式通用爬虫管理平台Crawlab》已介绍了Crawlab的架构以及安装使用,这里快速介绍一下如何安装、运行、使用Crawlab。安装到Crawlab的Github Repo用克隆一份到本地。git clone https://github.com/tikazyq/crawlab安装相应的依赖包和库。cd crawlab# 安装python依赖pip install -r crawlab/requirements# 安装前端依赖cd frontendnpm install安装mongodb和redis-server。Crawlab将用MongoDB作为结果集以及运行操作的储存方式,Redis作为Celery的任务队列,因此需要安装这两个数据库。运行在运行之前需要对Crawlab进行一些配置,配置文件为config.py。# project variablesPROJECT_SOURCE_FILE_FOLDER = ‘/Users/yeqing/projects/crawlab/spiders’ # 爬虫源码根目录PROJECT_DEPLOY_FILE_FOLDER = ‘/var/crawlab’ # 爬虫部署根目录PROJECT_LOGS_FOLDER = ‘/var/logs/crawlab’ # 日志目录PROJECT_TMP_FOLDER = ‘/tmp’ # 临时文件目录# celery variablesBROKER_URL = ‘redis://192.168.99.100:6379/0’ # 中间者URL,连接redisCELERY_RESULT_BACKEND = ‘mongodb://192.168.99.100:27017/’ # CELERY后台URLCELERY_MONGODB_BACKEND_SETTINGS = { ‘database’: ‘crawlab_test’, ’taskmeta_collection’: ’tasks_celery’,}CELERY_TIMEZONE = ‘Asia/Shanghai’CELERY_ENABLE_UTC = True# flower variablesFLOWER_API_ENDPOINT = ‘http://localhost:5555/api’ # Flower服务地址# database variablesMONGO_HOST = ‘192.168.99.100’MONGO_PORT = 27017MONGO_DB = ‘crawlab_test’# flask variablesDEBUG = TrueFLASK_HOST = ‘127.0.0.1’FLASK_PORT = 8000启动后端API,也就是一个Flask App,可以直接启动,或者用gunicorn代替。cd ../crawlabpython app.py启动Flower服务(抱歉目前集成Flower到App服务中,必须单独启动来获取节点信息,后面的版本不需要这个操作)。python ./bin/run_flower.py启动本地Worker。在其他节点中如果想只是想执行任务的话,只需要启动这一个服务就可以了。python ./bin/run_worker.py启动前端服务器。cd ../frontendnpm run serve使用首页Home中可以看到总任务数、总爬虫数、在线节点数和总部署数,以及过去30天的任务运行数量。点击侧边栏的Spiders或者上方到Spiders数,可以进入到爬虫列表页。这些是爬虫源码根目录PROJECT_SOURCE_FILE_FOLDER下的爬虫。Crawlab会自动扫描该目录下的子目录,将子目录看作一个爬虫。Action列下有一些操作选项,点击部署Deploy按钮将爬虫部署到所有在线节点中。部署成功后,点击运行Run按钮,触发抓取任务。这时,任务应该已经在执行了。点击侧边栏的Tasks到任务列表,可以看到已经调度过的爬虫任务。基本使用就是这些,但是Crawlab还能做到更多,大家可以进一步探索,详情请见Github。PuppeteerPuppeteer是谷歌开源的基于Chromium和NodeJS的自动化测试工具,可以很方便的让程序模拟用户的操作,对浏览器进行程序化控制。Puppeteer有一些常用操作,例如点击,鼠标移动,滑动,截屏,下载文件等等。另外,Puppeteer很类似Selenium,可以定位浏览器中网页元素,将其数据抓取下来。因此,Puppeteer也成为了新的爬虫利器。相对于Selenium,Puppeteer是新的开源项目,而且是谷歌开发,可以使用很多新的特性。对于爬虫来说,如果前端知识足够的话,写数据抓取逻辑简直不能再简单。正如其名字一样,我们是在操作木偶人来帮我们抓取数据,是不是很贴切?掘金上已经有很多关于Puppeteer的教程了(爬虫利器 Puppeteer 实战、Puppeteer 与 Chrome Headless —— 从入门到爬虫),这里只简单介绍一下Puppeteer的安装和使用。安装安装很简单,就一行npm install命令,npm会自动下载Chromium并安装,这个时间会比较长。为了让安装好的puppeteer模块能够被所有nodejs爬虫所共享,我们在PROJECT_DEPLOY_FILE_FOLDER目录下安装node的包。# PROJECT_DEPLOY_FILE_FOLDER变量值cd /var/crawlab# 安装puppeteernpm i puppeteer# 安装mongodbnpm i mongodb安装mongodb是为了后续的数据库操作。使用以下是Copy/Paste的一段用Puppeteer访问简书然后截屏的代码,非常简洁。const puppeteer = require(‘puppeteer’);(async () => { const browser = await (puppeteer.launch()); const page = await browser.newPage(); await page.goto(‘https://www.jianshu.com/u/40909ea33e50'); await page.screenshot({ path: ‘jianshu.png’, type: ‘png’, // quality: 100, 只对jpg有效 fullPage: true, // 指定区域截图,clip和fullPage两者只能设置一个 // clip: { // x: 0, // y: 0, // width: 1000, // height: 40 // } }); browser.close();})();关于Puppeteer的常用操作,请移步《我常用的puppeteer爬虫api》。编写爬虫啰嗦了这么久,终于到了万众期待的爬虫时间了。Talk is cheap, show me the code!咦?我们不是已经Show了不少代码了么…由于我们的目标是建立一个技术文章聚合平台,我们需要去各大技术网站抓取文章。资源当然是越多越好。作为展示用,我们将抓取下面几个具有代表性的网站:掘金SegmentFaultCSDN研究发现这三个网站都是由Ajax获取文章列表,生成动态内容以作为传统的分页替代。这对于Puppeteer来说很容易处理,因为Puppeteer绕开了解析Ajax这一部分,浏览器会自动处理这样的操作和请求,我们只着重关注数据获取就行了。三个网站的抓取策略基本相同,我们以掘金为例着重讲解。掘金首先是引入Puppeteer和打开网页。const puppeteer = require(‘puppeteer’);const MongoClient = require(‘mongodb’).MongoClient;(async () => { // browser const browser = await (puppeteer.launch({ headless: true })); // define start url const url = ‘https://juejin.im’; // start a new page const page = await browser.newPage(); … })();headless设置为true可以让浏览器以headless的方式运行,也就是指浏览器不用在界面中打开,它会在后台运行,用户是看不到浏览器的。browser.newPage()将新生成一个标签页。后面的操作基本就围绕着生成的page来进行。接下来我们让浏览器导航到start url。 … // navigate to url try { await page.goto(url, {waitUntil: ‘domcontentloaded’}); await page.waitFor(2000); } catch (e) { console.error(e); // close browser browser.close(); // exit code 1 indicating an error happened code = 1; process.emit(“exit “); process.reallyExit(code); return } …这里try catch的操作是为了处理浏览器访问超时的错误。当访问超时时,设置exit code为1表示该任务失败了,这样Crawlab会将该任务状态设置为FAILURE。然后我们需要下拉页面让浏览器可以读取下一页。 … // scroll down to fetch more data for (let i = 0; i < 100; i++) { console.log(‘Pressing PageDown…’); await page.keyboard.press(‘PageDown’, 200); await page.waitFor(100); } …翻页完毕后,就开始抓取数据了。 … // scrape data const results = await page.evaluate(() => { let results = []; document.querySelectorAll(’.entry-list > .item’).forEach(el => { if (!el.querySelector(’.title’)) return; results.push({ url: ‘https://juejin.com’ + el.querySelector(’.title’).getAttribute(‘href’), title: el.querySelector(’.title’).innerText }); }); return results; }); …page.evaluate可以在浏览器Console中进行JS操作。这段代码其实可以直接在浏览器Console中直接运行。调试起来是不是方便到爽?前端工程师们,开始欢呼吧!获取了数据,接下来我们需要将其储存在数据库中。 … // open database connection const client = await MongoClient.connect(‘mongodb://192.168.99.100:27017’); let db = await client.db(‘crawlab_test’); const colName = process.env.CRAWLAB_COLLECTION || ‘results_juejin’; const taskId = process.env.CRAWLAB_TASK_ID; const col = db.collection(colName); // save to database for (let i = 0; i < results.length; i++) { // de-duplication const r = await col.findOne({url: results[i]}); if (r) continue; // assign taskID results[i].task_id = taskId; // insert row await col.insertOne(results[i]); } …这样,我们就将掘金最新的文章数据保存在了数据库中。其中,我们用url字段做了去重处理。CRAWLAB_COLLECTION和CRAWLAB_TASK_ID是Crawlab传过来的环境变量,分别是储存的collection和任务ID。任务ID需要以task_id为键保存起来,这样在Crawlab中就可以将数据与任务关联起来了。整个爬虫代码如下。const puppeteer = require(‘puppeteer’);const MongoClient = require(‘mongodb’).MongoClient;(async () => { // browser const browser = await (puppeteer.launch({ headless: true })); // define start url const url = ‘https://juejin.im’; // start a new page const page = await browser.newPage(); // navigate to url try { await page.goto(url, {waitUntil: ‘domcontentloaded’}); await page.waitFor(2000); } catch (e) { console.error(e); // close browser browser.close(); // exit code 1 indicating an error happened code = 1; process.emit(“exit “); process.reallyExit(code); return } // scroll down to fetch more data for (let i = 0; i < 100; i++) { console.log(‘Pressing PageDown…’); await page.keyboard.press(‘PageDown’, 200); await page.waitFor(100); } // scrape data const results = await page.evaluate(() => { let results = []; document.querySelectorAll(’.entry-list > .item’).forEach(el => { if (!el.querySelector(’.title’)) return; results.push({ url: ‘https://juejin.com’ + el.querySelector(’.title’).getAttribute(‘href’), title: el.querySelector(’.title’).innerText }); }); return results; }); // open database connection const client = await MongoClient.connect(‘mongodb://192.168.99.100:27017’); let db = await client.db(‘crawlab_test’); const colName = process.env.CRAWLAB_COLLECTION || ‘results_juejin’; const taskId = process.env.CRAWLAB_TASK_ID; const col = db.collection(colName); // save to database for (let i = 0; i < results.length; i++) { // de-duplication const r = await col.findOne({url: results[i]}); if (r) continue; // assign taskID results[i].task_id = taskId; // insert row await col.insertOne(results[i]); } console.log(results.length: ${results.length}); // close database connection client.close(); // shutdown browser browser.close();})();SegmentFault & CSDN这两个网站的爬虫代码基本与上面的爬虫一样,只是一些参数不一样而已。我们的爬虫项目结构如下。运行爬虫在Crawlab中打开Spiders,我们可以看到刚刚编写好的爬虫。点击各个爬虫的View查看按钮,进入到爬虫详情。在Execute Command中输入爬虫执行命令。对掘金爬虫来说,是node juejin_spider.js。输入完毕后点击Save保存。然后点击Deploy部署爬虫。最后点击Run运行爬虫。点击左上角到刷新按钮可以看到刚刚运行的爬虫任务已经在运行了。点击Create Time后可以进入到任务详情。Overview标签中可以看到任务信息,Log标签可以看到日志信息,Results信息中可以看到抓取结果。目前在Crawlab结果列表中还不支持数据导出,但是不久的版本中肯定会将导出功能加入进来。总结在这一小节,我们已经能够将Crawlab运行起来,并且能用Puppeteer编写抓取三大网站技术文章的爬虫,并且能够用Crawlab运行爬虫,并且读取抓取后的数据。下一节,我们将用Flask+Vue做一个简单的技术文章聚合网站。能看到这里的都是有耐心的好同学,赞一个。Github: tikazyq/crawlab如果感觉Crawlab还不错的话,请加作者微信拉入开发交流群,大家一起交流关于Crawlab的使用和开发。 ...

March 15, 2019 · 3 min · jiezi

Python爬虫笔记4-BeautifulSoup使用

BeautifulSoup介绍与lxml一样,BeautifulSoup也是一个HTML/XML的解析器,主要功能也是如何解析和提取HTML/XML数据。几种解析工具的对比工具速度难度 正则表达式最快困难 BeautifulSoup慢最简单 lxml快简单 lxml 只会局部遍历,而Beautiful Soup 是基于HTML DOM的,会载入整个文档,解析整个DOM树,因此时间和内存开销都会大很多,所以性能要低于lxml。安装我的环境是Python 3.6.5,windows下cmd里执行pip安装即可。pip3 install beautifulsoup4测试python终端里导入beautifulsoup,无报错信息即安装成功。>>from bs4 import BeautifulSoup>>BeautifulSoup对象BeautifulSoup将复杂的HTML文档转换成一个复杂的树形结构,每个节点都是Python对象,所有对象可以归纳为4种:TagNavigableStringBeautifulSoupCommentBeautifulSoup 对象表示的是一个文档的内容。大部分时候,可以把它当作 Tag 对象,是一个特殊的 Tag。Comment 对象是一个特殊类型的 NavigableString 对象,其输出的内容不包括注释符号。TagTag可以简单理解为HTML文档中的一个个的标签,比如:<head><title>The Dormouse’s story</title></head><ur><li class=“item-0”><a href=“link1.html”>first item</a></li></ur>上面HTML文档中的head、title、ur、li都是HTML标签(节点名称),这些标签加上里面的内容就是tag。获取Tags# 导入模块from bs4 import BeautifulSouphtml = “”"<html><head><title>The Dormouse’s story</title></head><body><p class=“title” name=“dromouse”><b>The Dormouse’s story</b></p><p class=“story”>Once upon a time there were three little sisters; and their names were<a href=“http://example.com/elsie" class=“sister” id=“link1”><!– Elsie –></a>,<a href=“http://example.com/lacie" class=“sister” id=“link2”>Lacie</a> and<a href=“http://example.com/tillie" class=“sister” id=“link3”>Tillie</a>;and they lived at the bottom of a well.</p><p class=“story”>…</p>”””# 初始化BeautifulSoup对象,指定lxml解析器soup = BeautifulSoup(html, ’lxml’)# prettify()方法格式化soup的内容print(soup.prettify())# soup.title选出title节点print(soup.title)# <title>The Dormouse’s story</title>print(type(soup.title))# <class ‘bs4.element.Tag’>print(soup.head)# <head><title>The Dormouse’s story</title></head>print(soup.p)# <p class=“title” name=“dromouse”><b>The Dormouse’s story</b></p>说明:使用soup加节点名称可以获取节点内容,这些对象的类型是bs4.element.Tag,但是它查找的是在内容中第一个符合要求的节点。比如上面代码有多个p标签,但是它只查找了第一个p标签。对于Tag有两个重要的属性,name和attrs。当选择一个节点后,name属性获取节点的名称,attrs属性获取节点的属性(以字典形式返回)。print(soup.name)# [document] #soup 对象本身比较特殊,它的 name 即为 [document]print(soup.head.name)# head #对于其他内部标签,输出的值便为标签本身的名称 print(soup.p.attrs)# {‘class’: [’title’], ’name’: ‘dromouse’}# 在这里,我们把 p 标签的所有属性打印输出了出来,得到的类型是一个字典。# 下面三种方法都可以获取字典里的值,是等价的,结果都一样print(soup.p.get(‘class’))# [’title’]print(soup.p[‘class’])# [’title’]print(soup.p.attrs[‘class’])# [’title’]# 还可以针对属性或者内容进行修改soup.p[‘class’] = “newClass"print (soup.p)# <p class=“newClass” name=“dromouse”><b>The Dormouse’s story</b></p>NavigableString获取了Tag,也就是获取了节点内容,但是只想要获取节点内部的内容怎么办?只需使用.string即可。# 获取节点内容print(soup.p.string)# The Dormouse’s storyprint(type(soup.p.string))# <class ‘bs4.element.NavigableString’>遍历文档树在选取节点的时候,也可以先选取一个节点,然后以这个节点为基准选取它的子节点,父节点,子孙节点等等,下面就介绍常用的选取方法。获取直接子节点.contents .children属性.contentstag的.contents属性可以将tag的直接子节点以列表的方式输出。下面例子选取head节点为基准,.contents选取head的子节点title,然后以列表返回。print(soup.head.contents)# [<title>The Dormouse’s story</title>]输出方式为列表,可以用列表索引来获取它的某一个元素.print(soup.head.contents[0])# <title>The Dormouse’s story</title>.childrenchildren属性和contents属性不同的是它返回的不是一个列表,而是一个生成器。可用for循环输出结果。print(soup.head.children)# <list_iterator object at 0x0000017415655588>for i in soup.head.children: print(i)# <title>The Dormouse’s story</title> 获取所有子孙节点:.descendants属性上面两个属性都只能获取到基准节点的下一个节点,要想获取节点的所有子孙节点,就可以使用descendants属性了。它返回的也是一个生成器。print(soup.descendants)# <generator object descendants at 0x0000028FFB17C4C0>还有其他属性如查找父节点,组父节点的属性就不记录了(平时很少用)。搜索文档树BeautifulSoup提供了一些查询方法(find_all,find等),调用对应方法,输入查询参数就可以得到我们想要的内容了,可以理解为搜索引擎的功能。(百度/谷歌=查询方法,查询内容=查询参数,返回的网页=想要的内容)下面介绍最常用的find_all方法。find_all方法作用:查找所有符合条件的元素,返回的是列表形式API:find_all(name, attrs, recursive, text, **kwargs)1. namename 参数可以根据节点名来查找元素。A. 传字符串最简单的过滤器是字符串.在搜索方法中传入一个字符串参数,BeautifulSoup会查找与字符串完整匹配的内容,下面的例子用于查找文档中所有的<p>标签。print(soup.find_all(‘p’))# 通常以下面方式写比较好print(soup.find_all(name=‘p’))B.传正则表达式 如果传入正则表达式作为参数,Beautiful Soup会通过正则表达式的 match() 来匹配内容.下面例子中找出所有以p开头的标签。import reprint(soup.find_all(re.compile(’^p’)))C.传列表如果传入列表参数,BeautifulSoup会将与列表中任一元素匹配的内容返回。下面代码会找到HTML代码中的head标签和b标签。print(soup.find_all([‘head’,‘b’]))# [<head><title>The Dormouse’s story</title></head>, <b>The Dormouse’s story</b>]2. attrsfind_all中attrs参数可以根据节点属性查询。查询时传入的参数是字典类型。比如查询id=link1的节点print(soup.find_all(attrs={‘id’:’link1’}))# [<a class=“sister” href=“http://example.com/elsie” id=“link1”><!– Elsie –></a>]对于常见的属性,可以不用以attrs来传递,直接传入查询参数即可。比如id,class_(class为Python关键字,使用下划线区分),如下:print(soup.find_all(id=‘link1’))print(soup.find_all(class_=‘sister’))运行结果:[<a class=“sister” href=“http://example.com/elsie” id=“link1”><!– Elsie –></a>][<a class=“sister” href=“http://example.com/elsie” id=“link1”><!– Elsie –></a>, <a class=“sister” href=“http://example.com/lacie” id=“link2”>Lacie</a>, <a class=“sister” href=“http://example.com/tillie” id=“link3”>Tillie</a>]3. texttext 参数可以搜搜文档中的字符串内容,与 name 参数的可选值一样, text 参数接受 字符串 , 正则表达式 , 列表。下面代码查找节点里内容中有story字符串的节点,并返回节点的内容。print(soup.find_all(text=re.compile(‘story’)))# [“The Dormouse’s story”, “The Dormouse’s story”]find方法find方法与find_all方法的区别:find_all:查询符合所有条件的元素,返回列表。find:只查找第一个匹配到的元素,返回单个元素,类型tag。查询方法与find_all大同小异。示例:print(soup.find(name=‘p’)) # 查询第一个p标签print(soup.find(text=re.compile(‘story’))) # 查找第一个节点内容中有story字符串的节点内容运行结果:<p class=“title” name=“dromouse”><b>The Dormouse’s story</b></p>The Dormouse’s story关于BeautifulSoup的使用就这样吧,常用个人就觉得用好find_all即可(=.=~)参考链接崔庆才 [Python3网络爬虫开发实战]:4.2-使用Beautiful Soup ...

March 15, 2019 · 2 min · jiezi

如何实现一个Python爬虫框架

这篇文章的题目有点大,但这并不是说我自觉对Python爬虫这块有多大见解,我只不过是想将自己的一些经验付诸于笔,对于如何写一个爬虫框架,我想一步一步地结合具体代码来讲述如何从零开始编写一个自己的爬虫框架2018年到如今,我花精力比较多的一个开源项目算是Ruia了,这是一个基于Python3.6+的异步爬虫框架,当时也获得一些推荐,比如Github Trending Python语言榜单第二,目前Ruia还在开发中,Star数目不过700+,如果各位有兴趣,欢迎一起开发,来波star我也不会拒绝哈什么是爬虫框架说这个之前,得先说说什么是框架:是实现业界标准的组件规范:比如众所周知的MVC开发规范提供规范所要求之基础功能的软件产品:比如Django框架就是MVC的开发框架,但它还提供了其他基础功能帮助我们快速开发,比如中间件、认证系统等框架的关注点在于规范二字,好,我们要写的Python爬虫框架规范是什么?很简单,爬虫框架就是对爬虫流程规范的实现,不清楚的朋友可以看上一篇文章谈谈对Python爬虫的理解,下面总结一下爬虫流程:请求&响应解析持久化这三个流程有没有可能以一种优雅的形式串联起来,Ruia目前是这样实现的,请看代码示例:可以看到,Item & Field类结合一起实现了字段的解析提取,Spider类结合Request * Response类实现了对爬虫程序整体的控制,从而可以如同流水线一般编写爬虫,最后返回的item可以根据使用者自身的需求进行持久化,这几行代码,我们就实现了获取目标网页请求、字段解析提取、持久化这三个流程实现了基本流程规范之后,我们继而就可以考虑一些基础功能,让使用者编写爬虫可以更加轻松,比如:中间件(Ruia里面的Middleware)、提供一些hook让用户编写爬虫更方便(比如ruia-motor)这些想明白之后,接下来就可以愉快地编写自己心目中的爬虫框架了如何踏出第一步首先,我对Ruia爬虫框架的定位很清楚,基于asyncio & aiohttp的一个轻量的、异步爬虫框架,怎么实现呢,我觉得以下几点需要遵守:轻量级,专注于抓取、解析和良好的API接口插件化,各个模块耦合程度尽量低,目的是容易编写自定义插件速度,异步无阻塞框架,需要对速度有一定追求什么是爬虫框架如今我们已经很清楚了,现在急需要做的就是将流程规范利用Python语言实现出来,怎么实现,分为哪几个模块,可以看如下图示:同时让我们结合上面一节的Ruia代码来从业务逻辑角度看看这几个模块到底是什么意思:Request:请求Response:响应Item & Field:解析提取Spider:爬虫程序的控制中心,将请求、响应、解析、存储结合起来这四个部分我们可以简单地使用五个类来实现,在开始讲解之前,请先克隆Ruia框架到本地:# 请确保本地Python环境是3.6+git clone https://github.com/howie6879/ruia.git# 安装pipenvpip install pipenv # 安装依赖包pipenv install –dev然后用PyCharm打开Ruia项目:选择刚刚pipenv配置好的python解释器:此时可以完整地看到项目代码:好,环境以及源码准备完毕,接下来将结合代码讲述一个爬虫框架的编写流程Request & ResponseRequest类的目的是对aiohttp加一层封装进行模拟请求,功能如下:封装GET、POST两种请求方式增加回调机制自定义重试次数、休眠时间、超时、重试解决方案、请求是否成功验证等功能将返回的一系列数据封装成Response类返回接下来就简单了,不过就是实现上述需求,首先,需要实现一个函数来抓取目标url,比如命名为fetch:import asyncioimport aiohttpimport async_timeoutfrom typing import Coroutineclass Request: # Default config REQUEST_CONFIG = { ‘RETRIES’: 3, ‘DELAY’: 0, ‘TIMEOUT’: 10, ‘RETRY_FUNC’: Coroutine, ‘VALID’: Coroutine } METHOD = [‘GET’, ‘POST’] def init(self, url, method=‘GET’, request_config=None, request_session=None): self.url = url self.method = method.upper() self.request_config = request_config or self.REQUEST_CONFIG self.request_session = request_session @property def current_request_session(self): if self.request_session is None: self.request_session = aiohttp.ClientSession() self.close_request_session = True return self.request_session async def fetch(self): “““Fetch all the information by using aiohttp””” if self.request_config.get(‘DELAY’, 0) > 0: await asyncio.sleep(self.request_config[‘DELAY’]) timeout = self.request_config.get(‘TIMEOUT’, 10) async with async_timeout.timeout(timeout): resp = await self._make_request() try: resp_data = await resp.text() except UnicodeDecodeError: resp_data = await resp.read() resp_dict = dict( rl=self.url, method=self.method, encoding=resp.get_encoding(), html=resp_data, cookies=resp.cookies, headers=resp.headers, status=resp.status, history=resp.history ) await self.request_session.close() return type(‘Response’, (), resp_dict) async def _make_request(self): if self.method == ‘GET’: request_func = self.current_request_session.get(self.url) else: request_func = self.current_request_session.post(self.url) resp = await request_func return respif name == ‘main’: loop = asyncio.get_event_loop() resp = loop.run_until_complete(Request(‘https://docs.python-ruia.org/').fetch()) print(resp.status)实际运行一下,会输出请求状态200,就这样简单封装一下,我们已经有了自己的请求类Request,接下来只需要再完善一下重试机制以及将返回的属性封装一下就基本完成了:# 重试函数async def _retry(self): if self.retry_times > 0: retry_times = self.request_config.get(‘RETRIES’, 3) - self.retry_times + 1 self.retry_times -= 1 retry_func = self.request_config.get(‘RETRY_FUNC’) if retry_func and iscoroutinefunction(retry_func): request_ins = await retry_func(weakref.proxy(self)) if isinstance(request_ins, Request): return await request_ins.fetch() return await self.fetch()最终代码见ruia/request.py即可,接下来就可以利用Request来实际请求一个目标网页,如下:这段代码请求了目标网页https://docs.python-ruia.org/并返回了Response对象,其中Response提供属性介绍如下:Field & Item实现了对目标网页的请求,接下来就是对目标网页进行字段提取,我觉得ORM的思想很适合用在这里,我们只需要定义一个Item类,类里面每个属性都可以用Field类来定义,然后只需要传入url或者html,执行过后Item类里面 定义的属性会自动被提取出来变成目标字段值可能说起来比较拗口,下面直接演示一下可能你就明白这样写的好,假设你的需求是获取HackerNews网页的title和url,可以这样实现:import asynciofrom ruia import AttrField, TextField, Itemclass HackerNewsItem(Item): target_item = TextField(css_select=‘tr.athing’) title = TextField(css_select=‘a.storylink’) url = AttrField(css_select=‘a.storylink’, attr=‘href’)async def main(): async for item in HackerNewsItem.get_items(url=“https://news.ycombinator.com/"): print(item.title, item.url)if name == ‘main’: items = asyncio.run(main())从输出结果可以看到,title和url属性已经被赋与实际的目标值,这样写起来是不是很简洁清晰也很明了呢?来看看怎么实现,Field类的目的是提供多种方式让开发者提取网页字段,比如:XPathCSS SelectorRE所以我们只需要根据需求,定义父类然后再利用不同的提取方式实现子类即可,代码如下:class BaseField(object): "”" BaseField class """ def init(self, default: str = ‘’, many: bool = False): """ Init BaseField class url: http://lxml.de/index.html :param default: default value :param many: if there are many fields in one page """ self.default = default self.many = many def extract(self, *args, **kwargs): raise NotImplementedError(’extract is not implemented.’)class _LxmlElementField(BaseField): passclass AttrField(_LxmlElementField): """ This field is used to get attribute. """ passclass HtmlField(_LxmlElementField): """ This field is used to get raw html data. """ passclass TextField(_LxmlElementField): """ This field is used to get text. """ passclass RegexField(BaseField): """ This field is used to get raw html code by regular expression. RegexField uses standard library re inner, that is to say it has a better performance than _LxmlElementField. """ pass核心类就是上面的代码,具体实现请看ruia/field.py接下来继续说Item部分,这部分实际上是对ORM那块的实现,用到的知识点是元类,因为我们需要控制类的创建行为:class ItemMeta(type): """ Metaclass for an item """ def new(cls, name, bases, attrs): __fields = dict({(field_name, attrs.pop(field_name)) for field_name, object in list(attrs.items()) if isinstance(object, BaseField)}) attrs[’__fields’] = __fields new_class = type.new(cls, name, bases, attrs) return new_classclass Item(metaclass=ItemMeta): """ Item class for each item """ def init(self): self.ignore_item = False self.results = {}这一层弄明白接下来就很简单了,还记得上一篇文章《谈谈对Python爬虫的理解》里面说的四个类型的目标网页么:单页面单目标单页面多目标多页面单目标多页面多目标本质来说就是要获取网页的单目标以及多目标(多页面可以放在Spider那块实现),Item类只需要定义两个方法就能实现:get_item():单目标get_items():多目标,需要定义好target_item具体实现见:ruia/item.pySpider在Ruia框架中,为什么要有Spider,有以下原因:真实世界爬虫是多个页面的(或深度或广度),利用Spider可以对这些进行 有效的管理制定一套爬虫程序的编写标准,可以让开发者容易理解、交流,能迅速产出高质量爬虫程序自由地定制插件接下来说说代码实现,Ruia框架的API写法我有参考Scrapy,各个函数之间的联结也是使用回调,但是你也可以直接使用await,可以直接看代码示例:from ruia import AttrField, TextField, Item, Spiderclass HackerNewsItem(Item): target_item = TextField(css_select=‘tr.athing’) title = TextField(css_select=‘a.storylink’) url = AttrField(css_select=‘a.storylink’, attr=‘href’)class HackerNewsSpider(Spider): start_urls = [f’https://news.ycombinator.com/news?p={index}’ for index in range(1, 3)] async def parse(self, response): async for item in HackerNewsItem.get_items(html=response.html): yield itemif name == ‘main’: HackerNewsSpider.start()使用起来还是挺简洁的,输出如下:[2019:03:14 10:29:04] INFO Spider Spider started![2019:03:14 10:29:04] INFO Spider Worker started: 4380434912[2019:03:14 10:29:04] INFO Spider Worker started: 4380435048[2019:03:14 10:29:04] INFO Request <GET: https://news.ycombinator.com/news?p=1>[2019:03:14 10:29:04] INFO Request <GET: https://news.ycombinator.com/news?p=2>[2019:03:14 10:29:08] INFO Spider Stopping spider: Ruia[2019:03:14 10:29:08] INFO Spider Total requests: 2[2019:03:14 10:29:08] INFO Spider Time usage: 0:00:03.426335[2019:03:14 10:29:08] INFO Spider Spider finished!Spider的核心部分在于对请求URL的请求控制,目前采用的是生产消费者模式来处理,具体函数如下:详细代码,见ruia/spider.py更多至此,爬虫框架的核心部分已经实现完毕,基础功能同样一个不落地实现了,接下来要做的就是:实现更多优雅地功能实现更多的插件,让生态丰富起来修BUG项目地址点击阅读原文或者在github搜索ruia,如果你有兴趣,请参与进来吧!如果觉得写得不错,点个好看来个star呗 ...

March 15, 2019 · 3 min · jiezi

使用selenium实现批量文件下载

背景实现需求:批量下载联想某型号的全部驱动程序。一般在做网络爬虫的时候,都是保存网页信息为主,或者下载单个文件。当涉及到多文件批量下载的时候,由于下载所需时间不定,下载的文件名不定,所以有一定的困难。思路参数配置在涉及下载的时候,需要先对chromedriver进行参数配置,设定默认下载目录:global base_pathprofile = { ‘download.default_directory’: base_path}chrome_options = webdriver.ChromeOptions()chrome_options.add_experimental_option(‘prefs’, profile)driver = webdriver.Chrome(executable_path=’../common/chromedriver’, options=chrome_options)driver.implicitly_wait(10)页面分析联想官网上每个型号的驱动下载页面如上图所示,虽然前面有一个登陆的遮罩,但是实际上并不影响点击。需要注意的是:驱动列表,需要点击才可以显示具体的下载项目表格,否则可以找到对应元素但无法获取正确的信息driver_list.find_element_by_class_name(‘download-center_list_t_icon’).click()每个下载列表的表头建议做跳过处理if sub_list.find_element_by_class_name(‘download-center_usblist_td01’).text == ‘驱动名称’: continue下载处理在页面中,找到“普通下载”的元素,点击即可下载。最终实现结果是我们希望根据网页的列表进行重命名和重新归档到文件夹,但是我们会发现如下几个问题:下载过来的文件名无法控制。依次下载的话,我们无法确认需要下载多久。并行下载的话,无法有效的区分重命名。在网上找了很久,也没找到在下载时直接重命名的方法,所以最终选择依次下载,当每次下载完成后进行重命名和归档,思路如下:对每个驱动目录,先新建一个文件夹,如:主板点击下载后开始下载文件通过os模块,找到下载目录中所有文件,并按创建时间排序,找到最新创建的文件由于未完成的文件后缀为.crdownload(chrome),那么根据后缀来判断是否已完成下载,未完成的话继续等待待下载完成,将文件重命名并剪切到开始建立的归档目录。这里需要注意的是,有些文件名中不能存在/符号,否则会导致重命名失败,需要做一下替换。在后期测试的时候,发现还有几个坑需要注意:在查找最新创建的文件时,需要注意.DS_Store文件的处理。(Mac系统,Windows则需要考虑thumbs.db)需要判断一下最新创建的文件是否为文件夹,可以通过filter函数来处理最新文件的排序查找实现如下:def sort_file(): # 排序文件 dir_link = base_path dir_lists = list(filter(check_file, os.listdir(dir_link))) if len(dir_lists) == 0: return ’’ else: dir_lists.sort(key=lambda fn: os.path.getmtime(dir_link + os.sep + fn)) return os.path.join(base_path, dir_lists[-1])def check_file(filename): # 忽略系统文件 if filename == ‘.DS_Store’ or filename == ’thumbs.db’: return False global base_path # 排除文件夹 return os.path.isfile(os.path.join(base_path, filename))总结最终实现效果如下:完整代码参考:https://github.com/keejo125/w…如果大家有更好的方法,也欢迎分享。

March 10, 2019 · 1 min · jiezi

用Node+wechaty写一个爬虫脚本每天定时给女朋友发暖心微信消息

wechatBot微信每日说,每日自动发送微信消息给你心爱的人项目介绍灵感来源在掘金看到了一篇《用Node + EJS写一个爬虫脚本每天定时女朋友发一封暖心邮件》后,在评论区偶然看到一位读者说可不可以用微信实现一下。然后最近刚好在做微信机器人的小项目,那就把这个定时任务放到微信上去做吧,说干就干,撸了一下午终于撸出来了。项目地址github:https://github.com/gengchen528/wechatBot使用库wechaty - 微信操作node-schedule - 定时任务superagent - 爬取页面信息cheerio - 抓取页面qrcode-terminal - 终端显示二维码功能定时给朋友发送每日天气提醒以及每日一句根据关键词自动加好友和自动拉群功能后续继续扩展吧…(你有好的想法也可以提pr)数据来源每日一句和上面的大佬一样也是来自one天气信息来自墨迹天气定时任务node-schedule非你莫属了,可以定时每个月、每个礼拜、每天具体什么时候执行什么任务实现效果由于是微信定时发送消息,较邮件来说,微信无法把图片和文字放在同一消息框中,所以美观度来说可能没有邮件好,不过文字进行排版后还是可以的,由于时间仓促,所以文字比较少,后续会继续增加内容;代码说明目录结构config: 存放公共变量和superagent的配置schedule: 任务调度的配置superagent: 获取每日一句和天气信息untils: 抽取的共用方法核心代码index.js关于微信的登录,定时任务的创建,发送信息的获取都在这个文件里/** * WechatBot * - https://github.com/gengchen528/wechatBot /const {Wechaty,Friendship} = require(‘wechaty’)const schedule = require(’./schedule/index’)const config = require(’./config/index’)const untils = require(’./untils/index’)const superagent = require(’./superagent/index’)// 二维码生成function onScan (qrcode, status) { require(‘qrcode-terminal’).generate(qrcode) // 在console端显示二维码 const qrcodeImageUrl = [ ‘https://api.qrserver.com/v1/create-qr-code/?data=’, encodeURIComponent(qrcode), ].join(’’) console.log(qrcodeImageUrl)}// 登录async function onLogin (user) { console.log(贴心小助理${user}登录了) // 登陆后创建定时任务 schedule.setSchedule(config.SENDDATE,()=>{ console.log(‘你的贴心小助理开始工作啦!’) main() })}//登出function onLogout(user) { console.log(${user} 登出)}// 自动加群功能async function onMessage (msg) { const contact = msg.from() // 发消息人 const content = msg.text() //消息内容 const room = msg.room() //是否是群消息 if(room){ console.log(群名: ${room.topic()} 发消息人: ${contact.name()} 内容: ${content}) }else { console.log(发消息人: ${contact.name()} 消息内容: ${content}) } if (msg.self()) { return } if(/微信每日说|每日说|微信机器人/.test(content)){ let keyRoom = await this.Room.find({topic: /^微信每日说/i}) if(keyRoom){ try{ await keyRoom.add(contact) await keyRoom.say(‘微信每日说:欢迎新朋友 ‘, contact) }catch (e) { console.error(e) } } }}// 自动加好友功能async function onFriendShip(friendship) { let logMsg try { logMsg = ‘添加好友’ + friendship.contact().name() console.log(logMsg) switch (friendship.type()) { /* * * 1. New Friend Request * * when request is set, we can get verify message from request.hello, * and accept this request by request.accept() / case Friendship.Type.Receive: if (/微信每日说|微信机器人|微信|每日说/i.test(friendship.hello())) { logMsg = ‘自动添加好友,因为验证信息中带关键字‘每日说’’ await friendship.accept() } else { logMsg = ‘没有通过验证 ’ + friendship.hello() } break /* * * 2. Friend Ship Confirmed * / case Friendship.Type.Confirm: logMsg = ‘friend ship confirmed with ’ + friendship.contact().name() break } } catch (e) { logMsg = e.message } console.log(logMsg)}// 自动发消息功能async function main() { let contact = await bot.Contact.find({name:config.NICKNAME}) || await bot.Contact.find({alias:config.NAME}) // 获取你要发送的联系人 let one = await superagent.getOne() //获取每日一句 let weather = await superagent.getWeather() //获取天气信息 let today = await untils.formatDate(new Date())//获取今天的日期 let memorialDay = untils.getDay(config.MEMORIAL_DAY)//获取纪念日天数 let str = today + ‘<br>’ + ‘今天是我们在一起的第’ + memorialDay + ‘天’ + ‘<br><br>今日天气早知道<br><br>’ + weather.weatherTips +’<br><br>’ +weather.todayWeather+ ‘每日一句:<br><br>’+one+’<br><br>’+’——来自最爱你的我’ await contact.say(str)//发送消息}const bot = new Wechaty()bot.on(‘scan’, onScan)bot.on(’login’, onLogin)bot.on(’logout’, onLogout)bot.on(‘message’, onMessage)bot.on(‘friendship’, onFriendShip)bot.start() .then(() => console.log(‘开始登陆微信’)) .catch(e => console.error(e))superagent/index.jsconst superagent = require(’../config/superagent’)const config = require(’../config/index’)const cheerio = require(‘cheerio’)async function getOne() { // 获取每日一句 let res = await superagent.req(config.ONE,‘GET’) let $ = cheerio.load(res.text) let todayOneList = $(’#carousel-one .carousel-inner .item’) let todayOne = $(todayOneList[0]).find(’.fp-one-cita’).text().replace(/(^\s)|(\s*$)/g, “”) return todayOne;}async function getWeather() { //获取墨迹天气 let url = config.MOJI_HOST+config.CITY+’/’+config.LOCATION let res = await superagent.req(url,‘GET’) let $ = cheerio.load(res.text) let weatherTips = $(’.wea_tips em’).text() const today = $(’.forecast .days’).first().find(’li’); let todayInfo = { Day:$(today[0]).text().replace(/(^\s*)|(\s*$)/g, “”), WeatherText:$(today[1]).text().replace(/(^\s*)|(\s*$)/g, “”), Temp:$(today[2]).text().replace(/(^\s*)|(\s*$)/g, “”), Wind:$(today[3]).find(’em’).text().replace(/(^\s*)|(\s*$)/g, “”), WindLevel:$(today[3]).find(‘b’).text().replace(/(^\s*)|(\s*$)/g, “”), PollutionLevel:$(today[4]).find(‘strong’).text().replace(/(^\s*)|(\s*$)/g, “”) } let obj = { weatherTips:weatherTips, todayWeather:todayInfo.Day + ‘:’ + todayInfo.WeatherText + ‘<br>’ + ‘温度:’ + todayInfo.Temp + ‘<br>’ + todayInfo.Wind + todayInfo.WindLevel + ‘<br>’ + ‘空气:’ + todayInfo.PollutionLevel + ‘<br>’ } return obj}module.exports ={ getOne,getWeather}项目运行由于需要安装chromium, 所以要先配置一下镜像,注意由于wechaty的限制,最好使用node10以上版本npmnpm config set registry https://registry.npm.taobao.orgnpm config set disturl https://npm.taobao.org/distnpm config set puppeteer_download_host https://npm.taobao.org/mirrorsyarnyarn config set registry https://registry.npm.taobao.orgyarn config set disturl https://npm.taobao.org/distyarn config set puppeteer_download_host https://npm.taobao.org/mirrors然后进行项目安装git clone git@github.com:gengchen528/wechatBot.gitcd wechatBotnpm install 或 cnpm install参数配置wechatBot/config/index.js// 配置文件// 配置文件module.exports ={ ONE:‘http://wufazhuce.com/',//ONE的web版网站 MOJI_HOST:‘https://tianqi.moji.com/weather/china/', //中国墨迹天气url CITY:‘shanghai’,//收信者所在城市 LOCATION:‘pudong-new-district’,//收信者所在区 MEMORIAL_DAY:‘2015/04/18’, //你和收信者的纪念日 NAME:‘Leo_chen’,//微信备注姓名 NICKNAME:‘Leo_chen’, //微信昵称 SENDDATE:‘30 * * * * *’,//定时发送时间,规则见 /schedule/index.js}开始运行npm run start然后掏出你的手机,最好使用小号,扫描控制台的二维码即可待解决问题由于微信登录和邮件登录不同,所以无法使用进程守护工具,目前没有测试是否能够长时间登录因为node的原因,如果发生错误,可能会导致任务无法进行,需要手动重启并登录最好能够使用小号登录,如果是常用微信登录,在电脑客户端登陆后可能会wechaty挤掉墨迹天气页面在获取的时候可能会存在延时,有时可能获取不到后续功能为了防止占用你的微信号,你和你的爱人添加我的微信后。你发送指定内容,我将会每天帮你发送消息还有在思考中…(你有好的想法也可以提出来)最后因为给这个微信加了自动加好友和拉群功能,所以有兴趣的小伙伴可以加我的微信进行测试,记得在加好友的时候带上暗号:微信每日说,然后发送微信每日说我会把你拉到群中(由于wechaty网页接口变更,目前自动拉群功能,免费版已经不能使用,只可以自动加好友,不过我会手动拉你进群 ????)赶快亲自试一试吧,相信你会挖掘出更多好玩的功能github:https://github.com/gengchen528/wechatBot ...

March 1, 2019 · 3 min · jiezi

某数加密的流程与原理简析

啃了这么长时间,基本上已经把某数的套路摸了个八九不离十,不愧是中国反爬界的集大成者,感觉收获满满,这里就简单记录一下分析成果。注意:某数在不同的网站上有不同的版本,其流程也略有不同,这里的流程不一定适用于其它网站。工具和资料之前的文章1 - 记录了之前尝试的其它方法之前的文章2 - 对加密混淆后的js的一些初步分析awesome-java-crawler - 我收集的爬虫相关工具和资料java-curl - 我编写的java HTTP库另一个用了某数加密的网站的破解SDK前端流程请求页面,返回应答;如果有之前生成的有效FSSBBIl1UgzbN7N80T cookie值,请求时须带上。应答的html中,包含以下关键数据:<meta id=“9DhefwqGPrzGxEp9hPaoag”>元素的content,这是加密后的数据,里面包含字符串映射表、全局方法映射表、加解密算法密钥等;注意有些网站的meta.id不同<script r=“m”>的元素,其中有一个是外部js链接,另一个则包含引导JS代码段,后面称为bootstrap.js。这段代码是动态生成的,每次请求均不相同。如果应答中包含FSSBBIl1UgzbN7N80T的cookie,保存起来,后面更新此cookie时会用到前面的外部js链接,其实内容是静态的,可以预先拿到并保存起来,里面的关键代码是:$ts.FxJzG50F = ‘……’;这个FxJzG50F的值就是加密后的核心JS代码段,后面称为main.js。这里比较奇妙的是:虽然加密后的内容是固定的,但是解密出来的JS里面的变量名、方法名、方法顺序却是随机的!执行bootstrap.js,包含以下关键步骤:把常用值、常用方法赋值给全局变量,以迷惑黑客,比如$wy = window; _$wG = undefined; _$a1 = String.prototype.charAt获取页面中的meta的content,分若干步骤解密之,这会在window中添加一堆全局变量和全局方法,包括所有的常量字符串映射获取window.$_ts.FxJzG50F的值,结合meta中的数据,生成核心JS代码即main.js执行main.js,这里的步骤就太多了,罗列一些关键的吧:继续从meta中解密一部分关键数据在以下事件上挂钩子,以记录用户行为:鼠标事件,触屏事件,键盘事件,输入事件,滚屏事件,加速器事件,屏幕方向改变事件,电池充电事件,窗口隐藏/显示事件另外,按键、点击、滚屏等事件的钩子函数同时也会更新FSSBBIl1UgzbN7N80T的cookie值在以下网络请求相关对象或方法上挂钩子,令其在发起请求时后面自动添加MmEwMD参数:ActiveXObject, XMLHttpRequest, Request, fetch, HTMLFormElement.submit关键方法检测,测试eval等几个方法是否被替换成非native版本添加一个频繁执行的定时器,其作用是检测debugger语句是否生效,如果生效说明有黑客在调试检查selenium, webdriver, PhantomJS, HeadlessChrome等自动化框架的特征检查浏览器类型,收集浏览器特征,收集渲染器特征执行WebGL 3D渲染测试,执行Canvas 2D渲染测试,目前看其测试结果并未实际使用,但不排除其它网站会使用此数据标识浏览器指纹添加一个50秒的定时器,其作用为更新FSSBBIl1UgzbN7N80T的cookie值对FSSBBIl1UgzbN7N80T cookie值进行首次更新FSSBBIl1UgzbN7N80T cookie值的内容这是整个某数加密的核心了,每次请求,无论GET, POST,是否XHR,都会带着这个cookie此cookie的值是很多数据加密后的内容,这里就不详细说了,至少是包含前面收集到的浏览器特征和用户行为数据的,简单的伪造User-Agent肯定是绕不过去的更新前会验证和重用之前的FSSBBIl1UgzbN7N80T值根据其内容可知,某数的后端是可以知道请求来源于何种浏览器,用户点击链接前有何行为等,这样他们可以做到:验证是否大量请求来源于相同特征的浏览器有选择的禁用某些浏览器给用户行为存疑的来源下毒MmEwMD参数的内容这个参数只会在XHR, 表单提交等场合用到,感觉和FSSBBIl1UgzbN7N80T的作用有重复,不确定某数的后端是否真的会严格验证其内容其内容的主体部分和FSSBBIl1UgzbN7N80T一致,另外会在前面连接上URL的摘要值

February 26, 2019 · 1 min · jiezi

weekly 2019-02-15

我开始学习Python了,这样我可以用它作为后端语言来学,也可以用来刷Leetcode,学爬虫等等这周我学习了:Python基础语法BeautiuilSoup Request库学习记录在这里 前期语法还不熟,慢慢来,有空就刷刷题

February 15, 2019 · 1 min · jiezi

Python微型异步爬虫框架

AmipyPython微型异步爬虫框架(A micro asynchronous Python website crawler framework)基于Python 3.5 + 的异步async-await 框架,搭建一个模块化的微型异步爬虫。可以根据需求控制异步队列的长度和延迟时间等。配置了可以去重的布隆过滤器,网页内容正文过滤等,完全自主配置使用。GitHub地址:源码适用环境windows 7 +Python 3.5 +安装直接使用pip安装即可:pip install amipy基础命令1.查看当前路径下的可用命令,在DOS命令行下输入:>amipy会出现命令帮助界面。2.创建一个新的项目,在DOS命令行下输入:>amipy cproject myproject会在当前路径下创建一个Amipy爬虫项目myproject。如果想要创建在指定目录下,可以加上附加参数,-d,如:> amipy cproject myproject -d D:\somefolder项目myproject便会在路径D:somefolder下创建。项目的目录结构应该如下:–myproject |-spiders | |-init.py |-init.py |-settings.py其中:settings.py 为整个项目的配置文件,可以为整个项目下的爬虫安装共有的中间件,控制整个项目的请求并发数,设置日志级别、文件路径等。3.进入项目路径,创建一个新的爬虫,在DOS命令行下输入:>amipy cspider myspider此时在项目myproject目录下的spiders文件夹中会创建一个爬虫目录myspider,此时的项目结构为:–myproject |-spiders | |-init.py | |-myspider | | |-init.py | | |-cookies.info | | |-item.py | | |-settings.py | | |-site_record.info | | |-spider.py | | |-url_record.info |-init.py |-settings.py |-log.log其中:位于myspider文件夹下的settings.py为爬虫myspider的配置文件,该配置只对当前爬虫有效。可以对该爬虫的布隆过滤器进行配置,安装中间件等。cookies.info 为爬虫的请求cookie保存文件,该爬虫爬过的所有网站的cookie会保存进该文件。可以通过爬虫配置文件settings.py进行路径加载和保存。site_record.info 为爬虫爬取过的网站的布隆过滤器记录文件,方便下次爬取的时候加载,会把爬取过的网站自动去掉。防止重复爬取。url_record.info 为该爬虫发出的请求url+headers+method+数据的去重后集合,爬虫结束运行时,如果配置保存去重url集合。下次爬取时加载该文件可以自动过滤爬取过的所有url+headers+method+数据。item.py 为ORM的MongoDB数据集合对象,对应的类属性可以映射到数据库集合中的字段,类名为数据表名。spider.py 为当前爬虫的主要文件,自己编写爬取逻辑,提取规则和数据保存脚本等。4.运行项目下的所有爬虫,进入项目路径,在DOS命令行下输入:>amipy runproject则该项目下的所有爬虫会开始运行,如果不想运行某个爬虫,只需要加上参数 -e,如:>amipy runproject -e No1spider No2spider则名为“No1spider”、“No2spider”的爬虫均不会运行。5.运行指定的爬虫,进入项目路径,在DOS命令行下输入:>amipy runspider myspider01 则名为“myspider01”的爬虫便会被启动。可以加上多个爬虫名称,用空格隔开即可。6.列出当前项目下的所有爬虫信息。在DOS命令行下输入:>amipy list便会将当前项目下的所有爬虫信息列出。使用Amipy爬虫编写流程编写自己的爬虫。【假设你已经安装前面"基础命令"创建了一个项目,并且创建了一个爬虫名为myspider】只需要进入myspider文件夹,按照需求修改当前爬虫的配置settings.py 以及数据存储需要用到的表模型item.py编写,编辑文件spider.py,加入爬取规则逻辑等。Url类对象Url类对象是一个规则匹配类,它提供了许多种模式的url规则匹配。比如:from amipy import Url# 表示匹配到正则模式’http://www.170mv.com/song.‘的所有链接Url(re=‘http://www.170mv.com/song.’)# 表示匹配到正则模式’http://www.170mv.com/song.‘的所有链接其回调函数为’getmp3’Url(re=‘http://www.170mv.com/song/.’,callback=‘getmp3’)# 表示匹配到地址为http协议,且路径为‘/novel/chapter1’,参数number=2的所有链接Url(scheme=‘http’,path=’/novel/chapter1’,params=‘number=2’)# 表示匹配到域名为www.baidu.com的所有链接,为该链接请求设置代理为'127.0.0.1:1080’Url(domain=‘www.baidu.com’,proxy=‘127.0.0.1:1080’)# 表示匹配到域名为www.baidu.com的所有链接,直接扔掉这些链接。Url(domain=‘www.baidu.com’,drop=True)Url类应用的还在于黑白名单属性中,如在爬虫类中的属性:whitelist = [ Url(re=‘http://www.170mv.com/song.’), Url(re=‘http..sycdn.kuwo.cn.’),]blacklist = [ Url(re=‘http://www.170mv.com/song.’), Url(re=‘http..sycdn.kuwo.cn.’),] 表示爬虫请求的url黑白名单匹配规则。必要属性打开spider.py ,可以看到有两个默认的必要属性:name 爬虫的唯一标识,项目下不能有该属性重名的爬虫。urls 起始链接种子,爬虫开始的url列表这两个属性是必须的。回调函数整个项目的主要实现在于回调函数的使用,利用异步请求得到响应后马上调用其请求绑定的回调函数来实现爬虫的异步爬取。请求后响应的回调函数(类方法)有:parse 返回状态200,请求正常响应正常,可以编写正常的规则提取、数据保存等。error 状态码非200,出现异常状态码,编写错误处理逻辑等。exception 请求出现异常,异常自定义处理。数据存储Amipy目前只支持MongoDB数据库,默认的数据库设置在爬虫配置文件settings.py中。对于爬取的数据进行保存,默认只使用MongoDB进行数据存储(后续可以自己扩展编写ORM)。只需要打开item.py,修改其中的示例类,原先为:from amipy.BaseClass.orm import Model,Fieldclass DataItemName(Model): …修改其内容为:from amipy.BaseClass.orm import Model,Fieldclass MyTableName(Model): ID = Field(‘索引’) content = Field(‘内容’)则类名 MyTableName为保存在指定数据库中的数据集合名称,ID为列对象,名称为“索引”,以此类推,content也为列对象,名称为“内容”。可以按照自己的需求进行添加删减列。数据的保存只需要在回调函数中对对应的列对象进行赋值,而后调用ORM对象的save函数即可。比如在spider.py的爬虫类中的成功回调函数parse中保存爬取到的数据: … def parse(self,response): self.item.ID = 200 self.item.content = ‘这是内容’ self.item.save() …则 数据集合 MyTableName中会自动保存一行数据:列“索引”为200,列“内容”为“这是内容”的数据行。引用orm数据模型对象只需要调用爬虫类的item属性,如上面示例中的self.item即是。获取其数据库对象可以使用:self.item.db来获得当前爬虫连接的MongoDB数据库对象。可以通过self.item.db.save()self.item.db.delete()self.item.db.update()…等api来实现数据库操作。事件循环loopAmipy爬虫的异步请求基于python3的协程async框架,所以项目全程只有一个事件循环运行,如果需要添加更多的爬虫请求,可以通过回调函数传进事件循环,加入请求队列。具体做法便是通过在爬虫类的回调函数中使用send函数来传递请求Request对象:import amipyfrom amipy import Request,sendclass MySpider(amipy.Spider): … def parse(self,response): … # 加入新的爬虫请求 url = ‘http://www.170mv.com/download/' send(Request(self,url)) …可以在项目配置文件settings.py中设置整个项目最大的协程并发数CONCURRENCY,以及协程请求的延时等。Telnet连接Amipy爬虫内置一个服务线程,可以通过Telnet进行连接来查看操作当前项目的爬虫,在启动爬虫后,可以通过新开一个DOS命令窗口,输入:>telnet 127.0.0.1 2232进行Telnet连接至项目服务线程,可以使用的命令有: show spiders show all running spiders and their conditions. list list a general situation of all spiders. echo echo a running spider and its attributes. pause pause a running spider by a give name. stop stop a running/paused spider by a give name. close close a spider by a give name. restart restart a stopped spider by a give name. resume resume a paused spider by a give name. quit quit the Spider-Client. help show all the available commands usage.举例,假设当前爬虫唯一标识名称为lianjia,则可以通过:$amipy> pause lianjia来暂停爬虫lianjia的爬取进度,在爬虫将当前请求队列清空后会一直暂停,直到收到Telnet端发出的其他命令。恢复爬虫使用:$amipy> resume lianjia查看当前项目下所有爬虫:$amipy> list详细查看则使用:$amipy> show spiders开启关闭Telnet在项目的配置文件settings.py中设置SPIDER_SERVER_ENABLE。例子1. 使用Amipy创建链家网爬虫(LianJiaSpider)爬虫目的:爬取链家网上北京当前最新的租房信息,包含“价格”,“房屋基本信息”、“配套设施”、“房源描述”、“联系经纪人”、“地址和交通”存入MongoDB数据库中创建项目进入到D:LianJia路径,创建Amipy项目LJproject:D:\LianJia> amipy cproject LJproject创建爬虫进入到项目路径D:LianJiaLJproject,创建Amipy爬虫lianjia:D:\LianJia\LJproject> amipy cspider lianjia编写数据库模型打开D:LianJiaLJprojectspidersLianjiaitem.py,编写数据保存模型:#coding:utf-8from amipy.BaseClass.orm import Model,Fieldclass LianJiaRenting(Model): price = Field(‘价格’) infos = Field(‘房屋基本信息’) facility = Field(‘配套设施’) desc = Field(‘房源描述’) agent = Field(‘联系经纪人’) addr = Field(‘地址与交通’)设置数据库连接打开 D:LianJiaLJprojectspidersLianjiasettings.py,找到MongoDB数据库连接设置,进行设置:# MongoDB settings for data saving.DATABASE_SETTINGS = { ‘host’:‘127.0.0.1’, ‘port’:27017, ‘user’:’’, ‘password’:’’, ‘database’:‘LianJiaDB’,}要先确保系统安装好MongoDB数据库并已经开启了服务。编写爬虫脚本打开 D:LianJiaLJprojectspidersLianjiaspider.py,编写爬虫采集脚本:import amipy,refrom amipy import send,Request,Urlfrom bs4 import BeautifulSoup as bs class LianjiaSpider(amipy.Spider): name = ’lianjia’ # 设置爬取初始链接 urls = [‘https://bj.lianjia.com/zufang/’] # 设置爬虫白名单,只允许爬取匹配的链接 whitelist = [ Url(re=‘https://bj.lianjia.com/zufang/.*’), ] # 自定义的属性 host =‘https://bj.lianjia.com’ page = 1 # 请求成功回调函数 def parse(self,response): soup = bs(response.text(),’lxml’) item_list = soup(‘div’,class_=‘content__list–item’) for i in item_list: # 获取详情页链接 并发送至爬虫请求队列 url = self.host+i.a[‘href’] send(Request(self,url,callback=self.details)) # 添加下一页 totalpage = soup(‘div’,class_=‘content__pg’)[0][‘data-totalpage’] if self.page>=int(totalpage): return self.page +=1 send(Request(self,self.host+’/zufang/pg{}/’.format(self.page))) def details(self,response): infos = {} agent = {} facility = [] soup = bs(response.text(),’lxml’) infos_li = soup(‘div’,class_=‘content__article__info’)[0].ul(’li’) facility_li = soup(‘ul’,class_=‘content__article__info2’)0 agent_ul = soup(‘ul’,id=‘agentList’)[0] addr_li = soup(‘div’,id=‘around’)[0].ul.li desc_li = soup(‘div’,id=‘desc’)[0].li desc_li.div.extract() desc = desc_li.p[‘data-desc’] if desc_li.p else ’’ for i in infos_li: text = i.text if ‘:’ in text: infos.update({text.split(’:’)[0]:text.split(’:’)[1]}) for i in facility_li[1:]: if ‘no’ not in i[‘class’][-2]: facility.append(i.text) for div in agent_ul(‘div’,class=‘desc’): name = div.a.text phone = div(‘div’,class_=‘phone’)[0].text agent[name]=phone # 数据模型对应并保存 self.item.desc = desc self.item.addr = re.sub(r’[\r\n ]’,’’,addr_li.text) if addr_li else ’’ self.item.price = soup(‘p’,class_=‘content__aside–title’)[0].text self.item.infos = infos self.item.agent = agent self.item.facility = facility self.item.save()如果在爬虫配置文件settings.py中设置遵守目标网站机器人协议可能会被禁止采集,可以自行关闭设置。另外,开启网页内容相似过滤BLOOMFILTER_HTML_ON可能会使爬取的结果数较少,爬虫只会采集相似度不同的网页内容的链接,如果需要大批量采集,而网页正文较少的,可以关闭这个设置。代码比较粗糙,但可以知道Amipy爬虫基本的实现流程。运行爬虫在项目根路径下,输入:D:\LianJia\LJproject> amipy runspider查看数据库进入MongoDB数据库:可以看到在数据库‘LianJiaDB’下的集合“LianJiaRenting”中已经保存有我们爬取的数据,格式如下:{ “_id” : ObjectId(“5c6541b065b2fd1cf002c565”), “价格” : “7500元/月 (季付价)”, “房屋基本信息” : { “发布” : “20天前”, “入住” : “随时入住”, “租期” : “2~3年”, “看房” : “暂无数据”, “楼层” : “中楼层/6层”, “电梯” : “无”, “车位” : “暂无数据”, “用水” : “民水”, “用电” : “民电”, “燃气” : “有”, “采暖” : “集中供暖” }, “配套设施” : [ “电视”, “冰箱”, “洗衣机”, “空调”, “热水器”, “床”, “暖气”, “宽带”, “衣柜”, “天然气” ], “房源描述” : “【交通出行】 小区门口为八里庄南里公交车站,75,675等多路公交经过。地铁6号线十里堡站D口,距离地铁口400米,交通十分方便,便于出行。<br />\n【周边配套】 此房位置棒棒哒,有建设银行,中国银行,交通银行,邮政储蓄,果多美水果超市,购物,金旭菜市场,娱乐,休闲,便利。旁边首航超市,姥姥家春饼,味多美蛋糕店,生活方便。<br />\n【小区介绍】 该小区中此楼是1981建成,安全舒适,小区内主力楼盘为6层板楼,前后无遮挡,此楼是多见的板楼,楼层高视野好。<br />\n”, “联系经纪人” : { “宋玉恒” : “4000124028转7907” }, “地址与交通” : “距离6号线-十里堡192m”}查看当前爬取进度新开一个DOS端口,输入:> telnet 127.0.0.1 2232进行Telnet连接,可以使用命令操作查看当前爬虫的爬取状态。例如使用echo命令:$amipy> echo lianjia可以查看当前爬虫的状态:—————-Spider-lianjia——————– Name:lianjia Status:RUNNING- Class:LianjiaSpider- Success:25 Fail:0 Exception:0- Priority:0- SeedUrls:[‘https://bj.lianjia.com/zufang/’]- Path:D:\LianJia\LJproject\spiders\Lianjia- Session:<aiohttp.client.ClientSession object at 0x000000000386FE10>- StartAt:Thu Feb 14 20:30:21 2019- PausedAt:None- ResumeAt:None- StopAt:None- RestartAt:None- CloseAt:None————————————————– ...

February 14, 2019 · 3 min · jiezi

一行js代码识别Selenium+Webdriver及其应对方案

有不少朋友在开发爬虫的过程中喜欢使用Selenium + Chromedriver,以为这样就能做到不被网站的反爬虫机制发现。先不说淘宝这种基于用户行为的反爬虫策略,仅仅是一个普通的小网站,使用一行Javascript代码,就能轻轻松松识别你是否使用了Selenium + Chromedriver模拟浏览器。我们来看一个例子。使用下面这一段代码启动Chrome窗口:from selenium.webdriver import Chromedriver = Chrome()现在,在这个窗口中打开开发者工具,并定位到Console选项卡,如下图所示。现在,在这个窗口输入如下的js代码并按下回车键:window.navigator.webdriver可以看到,开发者工具返回了true。如下图所示。但是,如果你打开一个普通的Chrome窗口,执行相同的命令,可以发现这行代码的返回值为undefined,如下图所示。所以,如果网站通过js代码获取这个参数,返回值为undefined说明是正常的浏览器,返回true说明用的是Selenium模拟浏览器。一抓一个准。这里给出一个检测Selenium的js代码例子:webdriver = window.navigator.webdriver;if(webdriver){ console.log(‘你这个傻逼你以为使用Selenium模拟浏览器就可以了?’)} else { console.log(‘正常浏览器’)}网站只要在页面加载的时候运行这个js代码,就可以识别访问者是不是用的Selenium模拟浏览器。如果是,就禁止访问或者触发其他反爬虫的机制。那么对于这种情况,在爬虫开发的过程中如何防止这个参数告诉网站你在模拟浏览器呢?可能有一些会js的朋友觉得可以通过覆盖这个参数从而隐藏自己,但实际上这个值是不能被覆盖的:对js更精通的朋友,可能会使用下面这一段代码来实现:Object.defineProperties(navigator, {webdriver:{get:()=>undefined}});运行效果如下图所示:确实修改成功了。这种写法就万无一失了吗?并不是这样的,如果此时你在模拟浏览器中通过点击链接、输入网址进入另一个页面,或者开启新的窗口,你会发现,window.navigator.webdriver又变成了true。如下图所示。那么是不是可以在每一个页面都打开以后,再次通过webdriver执行上面的js代码,从而实现在每个页面都把window.navigator.webdriver设置为undefined呢?也不行。因为当你执行:driver.get(网址)的时候,浏览器会打开网站,加载页面并运行网站自带的js代码。所以在你重设window.navigator.webdriver之前,实际上网站早就已经知道你是模拟浏览器了。接下来,又有朋友提出,可以通过编写Chrome插件来解决这个问题,让插件里面的js代码在网站自带的所有js代码之前执行。这样做当然可以,不过有更简单的办法,只需要设置Chromedriver的启动参数即可解决问题。在启动Chromedriver之前,为Chrome开启实验性功能参数excludeSwitches,它的值为[’enable-automation’],完整代码如下:from selenium.webdriver import Chromefrom selenium.webdriver import ChromeOptionsoption = ChromeOptions()option.add_experimental_option(’excludeSwitches’, [’enable-automation’])driver = Chrome(options=option)此时启动的Chrome窗口,在右上角会弹出一个提示,不用管它,不要点击停用按钮。再次在开发者工具的Console选项卡中查询window.navigator.webdriver,可以发现这个值已经自动变成undefined了。并且无论你打开新的网页,开启新的窗口还是点击链接进入其他页面,都不会让它变成true。运行效果如下图所示。截至2019年02月12日20:46分,本文所讲的方法可以用来登录知乎。如果使用 Selenium 直接登录知乎,会弹出验证码;先使用本文的方法再登录知乎,能够成功伪装成真实的浏览器,不会弹出验证码。实际上,Selenium + Webdriver能被识别的特征不止这一个。关于如何隐藏其他特征,请关注我的微信公众号。

February 13, 2019 · 1 min · jiezi

Python简单post信息

从自己别的博客搬过来的,很久之前的文章,正好放在专栏Python基础知识里不知道对别人有没有用,希望不会辣眼睛哈哈最近学了点关于python的网络爬虫的知识,简单记录一下,这里主要用到了requests库和BeautifulSoup库Requests is an elegant and simple HTTP library for Python, built for human beings.Beautiful Soup is a Python library for pulling data out of HTML and XML files. It works with your favorite parser to provide idiomatic ways of navigating, searching, and modifying the parse tree. It commonly saves programmers hours or days of work.以上是两个库的介绍,链接是文档信息1、示例页面这里我利用东北大学的图书馆的登陆页面来实现我们的爬虫功能(ps:没错,博主是东北大学的学生..所以我有账号密码),没有账号密码也没有关系,原理都是差不多的,之所以找这个页面,是因为这个页面没有验证码,能够简单一些,而且像学校的这种页面一般比较简单,方便操作2、简单分析首先我用的账户和密码登陆进了东北大学图书馆,我使用的是chrome浏览器,打开开发者模式,我们来看看我们提交了哪些信息。登陆进去后,按下F12打开开发者模式,在Network选项卡下面,我们找到这个文件,他的request方法是post,应该就是我们要找的文件了,拉到最下面看到Form Data,红色框出就是我们登陆时提交的信息了,一共五个部分,画红线的地方是账号和密码。搞清楚了post的信息后,我们就可以写代码来自动提交信息了。登陆部分搞清楚了,接下就要分析要抓取的信息了,现在我要抓取我的外借借阅历史列表预约请求要抓取这三个数据,如上图所示,我当前外借1本书,借阅过65本书,预约请求为0,现在的目的是将这些数据抓取出来,我们按下F12来查看网页的源代码,分析我们应该抓取哪一部分。如上图所示,一步步找到了数据所在的标签,我发现数据都在id=history这个标签下,所以可以先找到这个标签,然后再找tr标签,然后就能找到td标签里的数据了。3、实现的功能自动登陆抓取页面上的一些信息,并在控制台输出4、代码部分4.1、post数据的部分首先贴上这部分的代码def getHTMLText(url): try: kv = {‘user-agent’: ‘Mozilla/5.0’} mydata = {‘func’:’login-session’, ’login_source’:‘bor-info’, ‘bor_id’: ‘’, ‘bor_verification’: ‘’,‘bor_library’:‘NEU50’} re = requests.post(url, data=mydata, headers=kv) re.raise_for_status() re.encoding = re.apparent_encoding return re.text except: print(“异常”) return"“代码如上,我们来分析一下kv是为了模拟浏览器而定义的字典,因为有些网站如果识别出是爬虫的话,会拒绝访问,所以这里可以修改headers的信息来模拟浏览器登陆。mydata里面存的就是要post的信息,其中账号和密码我用代替了。requests.post()就是向指定的url 提交数据,关于requests在网上都能搜的到,就不赘述了。re.raise_for_status()这个的含义是如果访问失败的话,就会丢出异常。re.encoding = re.apparent_encoding修改编码,保证中文能被正确的解析。这里采用try except的结构,为了程序的健壮性考虑,让程序在错误的时候不至于崩溃。最后返回我们新的页面的text。4.2、抓取数据部分首先贴上代码def fillBookList(booklist, html): soup = BeautifulSoup(html,“html.parser”) for tr in soup.find(id=‘history’).descendants: if isinstance(tr, bs4.element.Tag): temp = tr.find_all(’td’) if len(temp)>0: booklist.append(temp[1].string.strip()) booklist.append(temp[3].string.strip()) booklist.append(temp[5].string.strip()) break参数分别是我们要填充的列表和目标页面创建一个BeautifulSoup的对象在整个页面中查找id=history的标签,然后遍历其所有子孙标签在遍历的过程中,标签的子标签可能是字符串类型,我们要过滤掉这些,所以用了isinstance(tr, bs4.element.Tag)isinstance 的用法:语法:isinstance(object, classinfo)其中,object 是变量,classinfo 是类型(tuple,dict,int,float,list,bool等) 和 class类若参数 object 是 classinfo 类的实例,或者 object 是 classinfo 类的子类的一个实例, 返回 True。 若 object 不是一个给定类型的的对象, 则返回结果总是False。若 classinfo 不是一种数据类型或者由数据类型构成的元组,将引发一个 TypeError 异常。在标签中寻找所有td标签,观察源代码发现,第一个td标签列表就是我们要的,所以一旦找到我们要的信息以后,就停止查找,并就信息存在booklist里面4.3、打印信息贴上代码def printUnivList(booklist): print(”{:^10}\t{:^6}\t{:^10}".format(“外借”,“借阅历史列表”,“预约请求”)) print("{:^10}\t{:^6}\t{:^10}".format(booklist[0],booklist[1],booklist[2])这部分很简单就不说了4.4、主函数贴上代码def main(): html = getHTMLText(“http://202.118.8.7:8991/F/-?func=bor-info”) booklist = [] fillBookList(booklist, html) printUnivList(booklist)5、测试成功的在控制台打印出了我们要的信息!6、完整的代码import requestsfrom bs4 import BeautifulSoupimport bs4def getHTMLText(url): try: kv = {‘user-agent’: ‘Mozilla/5.0’} mydata = {‘func’:’login-session’, ’login_source’:‘bor-info’, ‘bor_id’: ‘’, ‘bor_verification’: ‘*’,‘bor_library’:‘NEU50’} re = requests.post(url, data=mydata, headers=kv) re.raise_for_status() re.encoding = re.apparent_encoding return re.text except: print(“异常”) return"“def fillBookList(booklist, html): soup = BeautifulSoup(html,“html.parser”) for tr in soup.find(id=‘history’).descendants: if isinstance(tr, bs4.element.Tag): temp = tr.find_all(’td’) if len(temp)>0: booklist.append(temp[1].string.strip()) booklist.append(temp[3].string.strip()) booklist.append(temp[5].string.strip()) breakdef printUnivList(booklist): print(”{:^10}\t{:^6}\t{:^10}".format(“外借”,“借阅历史列表”,“预约请求”)) print("{:^10}\t{:^6}\t{:^10}".format(booklist[0],booklist[1],booklist[2]))def main(): html = getHTMLText(“http://202.118.8.7:8991/F/-?func=bor-info”) booklist = [] fillBookList(booklist, html) printUnivList(booklist)main() ...

February 3, 2019 · 2 min · jiezi

LogParser v0.8.0 发布:一个用于定期增量式解析 Scrapy 爬虫日志的 Python 库

GitHub 开源my8100 / logparser安装通过 pip:pip install logparser通过 git:git clone https://github.com/my8100/logparser.gitcd logparserpython setup.py install使用方法作为 service 运行请先确保当前主机已经安装和启动 Scrapyd通过命令 logparser 启动 LogParser访问 http://127.0.0.1:6800/logs/stats.json (假设 Scrapyd 运行于端口 6800)访问 http://127.0.0.1:6800/logs/projectname/spidername/jobid.json 以获取某个爬虫任务的日志分析详情配合 ScrapydWeb 实现爬虫进度可视化详见 my8100 / scrapydweb在 Python 代码中使用In [1]: from logparser import parseIn [2]: log = “““2018-10-23 18:28:34 [scrapy.utils.log] INFO: Scrapy 1.5.0 started (bot: demo) …: 2018-10-23 18:29:41 [scrapy.statscollectors] INFO: Dumping Scrapy stats: …: {‘downloader/exception_count’: 3, …: ‘downloader/exception_type_count/twisted.internet.error.TCPTimedOutError’: 3, …: ‘downloader/request_bytes’: 1336, …: ‘downloader/request_count’: 7, …: ‘downloader/request_method_count/GET’: 7, …: ‘downloader/response_bytes’: 1669, …: ‘downloader/response_count’: 4, …: ‘downloader/response_status_count/200’: 2, …: ‘downloader/response_status_count/302’: 1, …: ‘downloader/response_status_count/404’: 1, …: ‘dupefilter/filtered’: 1, …: ‘finish_reason’: ‘finished’, …: ‘finish_time’: datetime.datetime(2018, 10, 23, 10, 29, 41, 174719), …: ‘httperror/response_ignored_count’: 1, …: ‘httperror/response_ignored_status_count/404’: 1, …: ‘item_scraped_count’: 2, …: ’log_count/CRITICAL’: 5, …: ’log_count/DEBUG’: 14, …: ’log_count/ERROR’: 5, …: ’log_count/INFO’: 75, …: ’log_count/WARNING’: 3, …: ‘offsite/domains’: 1, …: ‘offsite/filtered’: 1, …: ‘request_depth_max’: 1, …: ‘response_received_count’: 3, …: ‘retry/count’: 2, …: ‘retry/max_reached’: 1, …: ‘retry/reason_count/twisted.internet.error.TCPTimedOutError’: 2, …: ‘scheduler/dequeued’: 7, …: ‘scheduler/dequeued/memory’: 7, …: ‘scheduler/enqueued’: 7, …: ‘scheduler/enqueued/memory’: 7, …: ‘start_time’: datetime.datetime(2018, 10, 23, 10, 28, 35, 70938)} …: 2018-10-23 18:29:42 [scrapy.core.engine] INFO: Spider closed (finished)“““In [3]: d = parse(log, headlines=1, taillines=1)In [4]: dOut[4]:OrderedDict([(‘head’, ‘2018-10-23 18:28:34 [scrapy.utils.log] INFO: Scrapy 1.5.0 started (bot: demo)’), (’tail’, ‘2018-10-23 18:29:42 [scrapy.core.engine] INFO: Spider closed (finished)’), (‘first_log_time’, ‘2018-10-23 18:28:34’), (’latest_log_time’, ‘2018-10-23 18:29:42’), (’elapsed’, ‘0:01:08’), (‘first_log_timestamp’, 1540290514), (’latest_log_timestamp’, 1540290582), (‘datas’, []), (‘pages’, 3), (‘items’, 2), (’latest_matches’, {‘resuming_crawl’: ‘’, ’latest_offsite’: ‘’, ’latest_duplicate’: ‘’, ’latest_crawl’: ‘’, ’latest_scrape’: ‘’, ’latest_item’: ‘’, ’latest_stat’: ‘’}), (’latest_crawl_timestamp’, 0), (’latest_scrape_timestamp’, 0), (’log_categories’, {‘critical_logs’: {‘count’: 5, ‘details’: []}, ’error_logs’: {‘count’: 5, ‘details’: []}, ‘warning_logs’: {‘count’: 3, ‘details’: []}, ‘redirect_logs’: {‘count’: 1, ‘details’: []}, ‘retry_logs’: {‘count’: 2, ‘details’: []}, ‘ignore_logs’: {‘count’: 1, ‘details’: []}}), (‘shutdown_reason’, ‘N/A’), (‘finish_reason’, ‘finished’), (’last_update_timestamp’, 1547559048), (’last_update_time’, ‘2019-01-15 21:30:48’)])In [5]: d[’elapsed’]Out[5]: ‘0:01:08’In [6]: d[‘pages’]Out[6]: 3In [7]: d[‘items’]Out[7]: 2In [8]: d[‘finish_reason’]Out[8]: ‘finished’ ...

January 24, 2019 · 2 min · jiezi

Selenium实现付费音乐批量下载

必备环境废话每年回家都要帮我爸下些音乐,这对我来说都是轻车熟路!可当我打开网易云点击下载按钮的时候,可惜已物是人非啦!开个 VIP 其实也不贵,临时用用也就¥15!但 IT 男的尊严必须要有,于是开始徜徉于搜索引擎中最后在知乎中,搜索到一个网址 VIP付费音乐解析P.S.再次感谢提供该服务的作者!如果你下载的音乐数量不多,直接这里搜索下载,下载后修改文件名即可!并且在这个网址中点击播放列表-点击同步,可以同步网易云的歌单!之后批量下载即是下载这些网易云的歌单!但是下载某个歌单中的几百首歌,手动下载就不现实了!在点击同步中需要输入你的网易云 UID,这 UID 的获取方式如下:第一步打开网易云随便选中一首歌,右键复制链接然后随便找个地方粘贴这个链接,例如https://music.163.com/song?id=25727803&userid=275613591最后这串数字就是 UID!程序运行环境第一步安装一个python3,这个简单吧!贴上我的版本 python3.65,安装时注意勾选Add in path第二步下载FFmpeg,这是用来解析视频和音频的,作为you-get的辅助工具,下载点这里,下载后解压添加环境变量即可第三步安装you-get,这是个下载视频音频的神器,有兴趣可以深入研究!之后我打算写个下载任意视频的工具,嘿嘿这是后话了!安装方式很简单pip install you-get环境配置就这样,还是非常轻松的,下面会解释下代码源码完整代码from selenium import webdriverfrom selenium.webdriver.common.action_chains import ActionChainsimport time, os# import thredingdef get_music_name_link(): main_handle = browser.current_window_handle fp = open(‘E:\Project_PY\file\musiclink.txt’,‘wb’) fp2 = open(‘E:\Project_PY\file\musicname.txt’,‘wb’) try: for i in list(range(2,400)): browser.switch_to_window(main_handle) txt = browser.find_element_by_xpath(’/html/body/div[3]/div/div[2]/div[2]/div[1]/div/div[%d]’ % i).text + ‘\n’ fp2.write(bytes(txt,encoding=‘utf-8’)) location = browser.find_element_by_xpath(’/html/body/div[3]/div/div[2]/div[2]/div[1]/div/div[%d]’ % i) ActionChains(browser).move_to_element(location).perform() browser.find_element_by_xpath(’/html/body/div[3]/div/div[2]/div[2]/div[1]/div/div[%d]/span[5]/div/span[2]’ % i).click() time.sleep(2) all_handles = browser.window_handles browser.switch_to_window(all_handles[-1]) # lastest url_link = browser.current_url + ‘\n’ fp.write(bytes(url_link,encoding=‘utf-8’)) browser.close() except Exception as e: print(‘get_music_name_link meet some problem! {}’.format(e)) fp.close() fp2.close()def download_music(list_name): with open(‘E:\Project_PY\file\musicname.txt’,‘r’,encoding=‘utf-8’) as fp1: music_name = fp1.readlines() len1 = len(music_name) fp2 = open(‘E:\Project_PY\file\musicname_format.txt’,‘w’,encoding=‘utf-8’) for i in range(3,len1,4): music_name_format = music_name[i].strip() + ‘\n’ fp2.write(music_name_format) fp2.close() with open(‘E:\Project_PY\file\musiclink.txt’,‘r’,encoding=‘utf-8’) as fp1: with open(‘E:\Project_PY\file\musicname_format.txt’,‘r’,encoding=‘utf-8’) as fp2: for music_link,music_name in zip(fp1.readlines(),fp2.readlines()): you_get_link = ‘you-get “{}” -o “E:\Project_PY\file\music\{}” -O “{}”’.format(music_link.strip(),list_name,music_name.strip()) you_get_link = you_get_link.strip() # print(you_get_link) os.system(you_get_link)url = ‘http://music.zhuolin.wang/'uid = input(‘please input your uid:’)options = webdriver.FirefoxOptions()options.add_argument(’–headless’)browser = webdriver.Firefox(firefox_options=options)browser.implicitly_wait(8)browser.get(url)# browser.maximize_window()browser.set_window_size(1000,100000)browser.find_element_by_xpath(’/html/body/div[3]/div/div[1]/div/span[3]’).click()# scroll = browser.find_element_by_xpath(’//*[@id=“mCSB_1_dragger_vertical”]’)# ActionChains(browser).drag_and_drop_by_offset(scroll,0,100).perform()# time.sleep(2)all_handles = browser.window_handlesbrowser.switch_to_window(all_handles[-1]) # lastesttime.sleep(1)browser.find_element_by_xpath(’/html/body/div[3]/div/div[2]/div[1]/div[1]/div/span/div[2]/span’).click()all_handles = browser.window_handlesbrowser.switch_to_window(all_handles[-1]) # lastesttime.sleep(1)browser.find_element_by_xpath(’/html/body/div[6]/div[2]/input’).send_keys(uid)browser.find_element_by_xpath(’/html/body/div[6]/div[3]/a[1]’).click()# t1 = threading.Thread(target=get_music_name)# t2 = threading.Thread(target=get_music_link)# t3 = threading.Thread(target=download_music)for i in list(range(3,100)): try: print(‘downloading song_list{}! please waiting….’.format(i)) browser.find_element_by_xpath(’/html/body/div[3]/div/div[2]/div[1]/div[1]/div/div[%d]/img’ % i).click() dir_name = browser.find_element_by_xpath(’/html/body/div[3]/div/div[2]/div[1]/div[1]/div/div[%d]/p’ % i).text time.sleep(1) get_music_name_link() download_music(dir_name) browser.find_element_by_xpath(’/html/body/div[3]/div/div[1]/div/span[3]’).click() time.sleep(1) all_handles = browser.window_handles browser.switch_to_window(all_handles[-1]) # lastest time.sleep(5) except Exception as e: print(‘get_song_list meet some problem! {}’.format(e))browser.quit()核心代码解释总共有三个函数:函数一get_music_name_link,主要是获取音乐名称以及音乐的下载链接函数二download_music,获取歌单名称,然后拼接下载链接和音乐名,调用you-get开始下载到对应目录函数三main,主要是利用 UID 获取歌单,以及批量下载歌单中的曲目需要注意的几个点:1.使用了sleep(1)休眠一秒,如果网络较慢需要将所有的sleep休眠时间加长2.所有的路径需要自己根据本机修改3.如果要修改代码一定要注意switch_to_window来切换窗口4.73行的for i in list(range(3,100))是用来选择下载的歌单,歌单从 1 开始计数程序演示输入网易云的 UID!然后静静的等待即可…此过程中会有部分音乐的播放声音,不喜欢可以开静音下当所有的链接解析完成后就会调用you-get下载,此过程会自动创建与歌单名相同的文件夹下载完成后P.S.如果要下载所有歌单,就不需要修改代码,直接输入网易云的 UID 运行即可!如果要下载某个具体的歌单只需要改动73行的这个循环for i in list(range(3,100)),所以说程序还是比较简单的,缺点可能就是没时间写 UI,而且也不太会 pyqt 之类的,只会点 MFC!所以将就用吧,功能还是很齐全的!END ...

January 23, 2019 · 2 min · jiezi

当年玩耍httpclient

当年玩耍httpclient前言httpclient是java开发中最常用的工具之一,通常大家会使用httpcilent去调用远程,使用其中比较基础的api,长期开发爬虫,会接触httpclient不常用的api,同时会遇到各式各样的坑,下面会总结这些年遇到的坑坑坑坑一:Received fatal alert: handshake_failure解决过程开发某省份移动爬虫时,加载首页会报标题错误,尝试各种办法都不好使,后来发现换了jdk1.8就好使经过长达一个星期源码探寻,发现错误源头是http在握手时,加密算法不支持导致jdk1.8以下版本不支持256位( TLS_DHE_RSA_WITH_AES_256_CBC_SHA )解决方案1、需要下载jce扩展包 http://www.oracle.com/technet...2、替换/jre/lib/security/里面的两个jar 3、覆盖后如果报错The jurisdiction policy files are not signed by a trusted signer!,说明下载的版本不对,要下对应jdk版本的二:Certificates does not conformto algorithm constraints解决过程用mvn打包时报错,security.cert.CertificateException: Certificates does not conform toalgorithm constraints原因是在java1.6之后的这个配置文件中,认为MD2的加密方式安全性太低,因而不支持这种加密方式,同时也不支持RSA长度小于1024的密文需要修改JAVA_HOME/jre/lib/security/java.security#jdk.certpath.disabledAlgorithms=MD2, RSA keySize < 1024但是这样做需要把每台机器都改一遍,如果新加机器忘记改了,就会发生问题,需要一套方法,只在代码层解决问题解决方案经查源码发现了触发问题的代码位置,通过强制继承SSLContextBuilder,并强制把private的keymanagers和trustmanagers的值置空就可以解决这个问题了static class MySSLContextBuilder extends SSLContextBuilder { static final String TLS = “TLS”; static final String SSL = “SSL”; private String protocol; private Set<KeyManager> keymanagers; private Set<TrustManager> trustmanagers; private SecureRandom secureRandom; public MySSLContextBuilder() { super(); this.keymanagers = new HashSet<KeyManager>(); this.trustmanagers = new HashSet<TrustManager>(); }}三:超时时间不生效解决过程很多人在使用httpclient时会到网上去找例子,例子中经常会有类似这样的设置 httpGet.getParams().setParameter(ClientPNames.HANDLE_REDIRECTS, !isAutoRelocal);使用上面方法时,发送httpclient时,在读取配置时如果发现getParams不为空,则会把以前设置的所有参数都不用了,而使用这里面设置的,所以超时时间会失效解决方案request.getParams().setParameter是过期方法,其中每一项参数在RequestConfig里都有对应的,遍历出来替换一遍即可boolean isRedirect = true; if(request != null) { HttpParams params = request.getParams(); if (params instanceof HttpParamsNames) { // 暂时只支持这个类型 isRedirect = params.getBooleanParameter( ClientPNames.HANDLE_REDIRECTS, true); } // 清空request request.setParams(new BasicHttpParams()); } if(timeOut > 0) { builder = RequestConfig.custom().setConnectionRequestTimeout(timeOut).setConnectTimeout(timeOut).setSocketTimeout(timeOut).setRedirectsEnabled(isRedirect).setCookieSpec(CookieSpecs.BEST_MATCH); } else { builder = RequestConfig.custom().setConnectionRequestTimeout(connectionTimeout).setConnectTimeout(connectionTimeout).setRedirectsEnabled(isRedirect).setSocketTimeout(socketTimeout).setCookieSpec(CookieSpecs.BEST_MATCH); }四:fildder监听问题问题开发爬虫经常会使用fildder来监控网络请求,但是使用httpclient时想用fildder会很难,网上查各种办法,经常都不好使,下面为大家来排个错,使用下面方法就可以完美解决这个问题,让fildder监控更容易解决方案首先java端// client builderHttpClientBuilder builder = HttpClients.custom();if(useFidder) { // 默认fidder写死 builder.setProxy(new HttpHost(“127.0.0.1”, 8888));}fildder端tools->fiddler options->https->actions->export root certificate to … <JDK_Home>\bin\keytool.exe -import -file C:\Users&lt;Username>\Desktop\FiddlerRoot.cer -keystore FiddlerKeystore -alias Fiddler五:支持gzip问题及方案有些网站返回进行了gzip压缩,返回内容是压缩的结果,需要解压HttpClient wrappedHttpClient = builder.setUserAgent(requestUA) .addInterceptorLast(new HttpResponseInterceptor() { @Override public void process(HttpResponse httpResponse, HttpContext httpContext) throws HttpException, IOException { HttpEntity httpEntity = httpResponse.getEntity(); Header header = httpEntity.getContentEncoding(); if (header != null) { for (HeaderElement element : header.getElements()) { if (“gzip”.equalsIgnoreCase(element.getName())) { httpResponse.setEntity(new GzipDecompressingEntity(httpResponse.getEntity())); } } } } })总结上面一些能想起来的坑,还会遇到很多问题,欢迎来讨论做一个广告:想简单开发爬虫的欢迎使用uncs作者:刘鹏飞 宜信技术学院 ...

January 23, 2019 · 1 min · jiezi

axios-extra 支持并发及自动重试功能的 axios

axios 基于 promise 用于 浏览器 和 node.js 的 http 客户端;而 axios-extra 扩展了 axios 让它拥有有并发控制以及重试的能力;如果你是一正在使用 axios 只要修改一行代码立即拥有安装npm i axios-extra使用默认最大 10 并发, 0 重试;//const axios = require(‘axios’); const axios = require(‘axios-extra’); //修改一行代码 无感使用 axios-extra设置并发数,及重试次数方式一: 用 axios.defaults 修改默认配制const axios = require(‘axios-extra’);axios.defaults.maxConcurrent = 1; //修改并发为1axios.defaults.queueOptions.retry = 2; //修改默认重试次数为2方式二: 用 axios.create(config) 创建新的 axiosconst axios = require(‘axios-extra’);let axios1 = axios.create({ maxConcurrent: 1, //并发为1 queueOptions: { retry: 3, //请求失败时,最多会重试3次 retryIsJump: true //是否立即重试, 否则将在请求队列尾部插入重试请求 }});方式三: 为某一次特殊请求单独设置重试设置config参数的queueOptions属性即可;const axios = require(‘axios-extra’);//本次get请求若不成功,将重试3次axios.get(‘http://xxx’,{ queueOptions: {retry: 3}})axios 的发送请求方法均可使用:axios(config)axios.request(config)axios.get(url[, config])axios.delete(url[, config])axios.head(url[, config])axios.options(url[, config])axios.post(url[, data[, config]])axios.put(url[, data[, config]])axios.patch(url[, data[, config]])补充并发与重试都是基于队列实现的, 默人重试是在队列的最后重新插入请求. retryIsJump 设置为true最会在队列头部插入请求, 实现立即重试更多 queueOptions 配制可参看这里 ...

January 23, 2019 · 1 min · jiezi

Jsoup爬虫获取自己网站在百度搜索中的实时排名

一直有一个需求,希望看到自己网站在百度的实时的排名用过一些工具,要么反应迟钝,要么结果不准确或不实时于是打算用jsoup写一个小爬虫来实时百度看网站排名直接上代码依赖只有jsoupjar包下载地址:https://mvnrepository.com/artifact/org.jsoup/jsoup或者引入maven依赖<dependency> <groupId>org.jsoup</groupId> <artifactId>jsoup</artifactId> <version>1.11.3</version></dependency>代码package com.zzzmh.spider;import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map;import org.jsoup.Jsoup;import org.jsoup.nodes.Document;import org.jsoup.nodes.Element;import org.jsoup.select.Elements;public class test { /** 百度搜索基本url 后面可以接的参数有 pn rn ie 等 / public final static String baseUrl = “https://www.baidu.com/s?ie=utf-8"; /* 连接超时时间 / public static int timeout = 30 * 1000; /* 连接重试次数 / public static int times = 10; /* UA / public static String UserAgent[] = { “Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50”, “Mozilla/5.0 (Windows; U; Windows NT 6.1; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50”, “Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; .NET4.0C; .NET4.0E; .NET CLR 2.0.50727; .NET CLR 3.0.30729; .NET CLR 3.5.30729; InfoPath.3; rv:11.0) like Gecko”, “Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E)”, “Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.87 Safari/537.36 OPR/37.0.2178.32”, “Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko”, “Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.116 UBrowser/5.6.12150.8 Safari/537.36”, “Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Safari/537.36 Edge/13.10586”, “Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.87 Safari/537.36 OPR/37.0.2178.32” }; /* 获取随机UA / public static String getRandomUA() { return UserAgent[(int) (Math.random() * (UserAgent.length))]; } /* 在这里进行连接 如果失败会继续重试 / public static Document getDocument(String url) { Document doc = null; for (int i = 0; i < times; i++) { try { doc = Jsoup.connect(url).header(“User-Agent”, getRandomUA()).timeout(timeout).get(); if (doc != null) break; } catch (Exception e) { e.printStackTrace(); } } return doc; } /* * 爬取百度指定关键字和页码的数据,只存id(排名号),title,url(百度会把url缩写) 亲测虽然 * 加了&rn=50可以明显增加效率,但结果和用户实际看到的有所出入,并不准确,故用默认rn,与用户实际看到保持一致 * * @param keyword 关键字 * @param page 页码 / public static List<Map<String, String>> spider(String keyword, int page) { List<Map<String, String>> result = new ArrayList<>(); try { Document document = getDocument(baseUrl + “&wd=” + keyword + “&pn=” + (page * 10)); Elements els = document.getElementsByClass(“result”); for (Element el : els) { Map<String, String> map = new HashMap<>(); try { map.put(“id”, el.attr(“id”)); map.put(“title”, el.getElementsByTag(“a”).get(0).text()); map.put(“url”, el.getElementsByClass(“f13”).get(0).getElementsByTag(“a”).text()); result.add(map); } catch (Exception e) { } } } catch (Exception e) { e.printStackTrace(); } return result; } /* * 分析在指定关键字在百度的排名 * * @param keyword 关键字 * @param url 要找的目标包含的url * @param maxPage 最多找几页,防止死循环 * @return 找得到返回map 超过最大页码还找不到返回null / public static Map<String, String> BaiduRanking(String keyword, String url, int maxPage) { System.out.println(“开始查找百度中关键字为 "” + keyword + “" 且url包含 "” + url + “" 的相关数据排名 最多查询 " + maxPage + “页”); for (int i = 0; i < maxPage; i++) { // 输出当前页码和个数,不需要输出可以去掉 System.out.println(“正在查询第” + i + “页中的第” + (i * 10 + 1) + " ~ " + ((i + 1) * 10) + “个”); List<Map<String, String>> list = spider(keyword, i); for (Map<String, String> map : list) { if (map.get(“url”).contains(url)) { return map; } } } return null; } public static void main(String[] args) { / * 例如 找关键字 极简壁纸 主要的网址特征 bz.zzzmh.cn 最多找20页 (相当于找1~200个中有无匹配) * 若有匹配返回 id title url * 若无匹配返回 Null */ System.out.println(BaiduRanking(“极简壁纸”, “zzzmh.cn”, 20)); }}效果1、网站标题: zzzmh’s blog 网站url: https://zzzmh.cn参数:关键字: “zzzmh” 目标包含url: “zzzmh.cn” 最多查询页数: “20"运行结果:开始查找百度中关键字为 “zzzmh” 且url包含 “zzzmh.cn” 的相关数据排名 最多查询 20页正在查询第0页中的第1 ~ 10个正在查询第1页中的第11 ~ 20个{id=13, title=zzzmh’s Blog - Design By zmh, url=https://zzzmh.cn/ 百度快照}2、网站标题: 极简壁纸… 网站url: https://bz.zzzmh.cn参数:关键字: “极简壁纸” 目标包含url: “zzzmh.cn” 最多查询页数: “20"运行结果:开始查找百度中关键字为 “极简壁纸” 且url包含 “zzzmh.cn” 的相关数据排名 最多查询 20页正在查询第0页中的第1 ~ 10个正在查询第1页中的第11 ~ 20个正在查询第2页中的第21 ~ 30个正在查询第3页中的第31 ~ 40个正在查询第4页中的第41 ~ 50个正在查询第5页中的第51 ~ 60个正在查询第6页中的第61 ~ 70个正在查询第7页中的第71 ~ 80个正在查询第8页中的第81 ~ 90个正在查询第9页中的第91 ~ 100个{id=93, title=极简壁纸_极致严选高清电脑桌面壁纸美图4k_最潮桌面壁纸网站, url=https://bz.zzzmh.cn/ 百度快照}3、网站标题: 极简插件… 网站url: https://chrome.zzzmh.cn参数:关键字: “极简插件” 目标包含url: “zzzmh.cn” 最多查询页数: “20"运行结果:开始查找百度中关键字为 “极简插件” 且url包含 “zzzmh.cn” 的相关数据排名 最多查询 20页正在查询第0页中的第1 ~ 10个正在查询第1页中的第11 ~ 20个正在查询第2页中的第21 ~ 30个正在查询第3页中的第31 ~ 40个正在查询第4页中的第41 ~ 50个正在查询第5页中的第51 ~ 60个正在查询第6页中的第61 ~ 70个正在查询第7页中的第71 ~ 80个正在查询第8页中的第81 ~ 90个正在查询第9页中的第91 ~ 100个正在查询第10页中的第101 ~ 110个正在查询第11页中的第111 ~ 120个正在查询第12页中的第121 ~ 130个正在查询第13页中的第131 ~ 140个正在查询第14页中的第141 ~ 150个正在查询第15页中的第151 ~ 160个正在查询第16页中的第161 ~ 170个正在查询第17页中的第171 ~ 180个正在查询第18页中的第181 ~ 190个正在查询第19页中的第191 ~ 200个null补充:有结果返回map包含id、title、url。没有结果返回 Null百度搜索的url可以指定rn页码,最多一页50个,使用后有效减少了连接次数。但亲测下来设置过rn以后的结果与实际用户在百度搜索的结果排序和个数都有出入。故选择用默认rn来检测,效果最准确。本篇博客也发表在了我的个人主页,欢迎查看,地址https://zzzmh.cn/single?id=58END ...

January 22, 2019 · 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

Charles 从入门到精通

内容清单Charles 的简介安装 CharlesCharles 初始化设置过滤网络请求截取HTTP/HTTPS数据模拟弱网环境修改网络请求修改服务器返回内容服务器压力测试反向代理解决与翻墙软件的冲突Charles 的简介Charles 是目前最主流的网络调试工具(Charles、Fiddler、Wireshark…)之一,对于一个开发者来说与网络打交道是日常需求,因此很多时候我们需要调试参数、返回的数据结构、查看网络请求的各种头信息、协议、响应时间等等。所以了解 Charles 并使用它Charles 通过将自己设置为系统的网络访问代理服务器,这样所有的网络请求都会通过它,从而实现了网路请求的截获和分析。Chareles 不仅可以分析电脑本机的网络请求(HTTP 和 HTTPS),还可以分析移动端设备的网络请求。Charles 是收费软件,作者开发出这样一个方便开发者使用的伟大工具,我们鼓励使用正版软件,但是对于一些囊中羞涩或者学生来说,有破解版的更好,别担心,这些我都准备好了,下一个 section 会讲解如何下载安装。安装 Charles方式1: Charles 官网地址,根据你的电脑操作系统选择合适的下载方式。此时下载下来的是需要收费的,不差钱的同学当然可以直接购买。购买链接方式2:按照方式1的方式去官网下载,然后下载相应 JAR包。这里以 MAC 为例,打 Finder,选择应用程序,选中 Charles,右击并选择“显示包内容”,看到 Contents 目录,点击进去选择 Java 文件夹,将下载下来的 JAR包 拖进去替换。至此,完成了 Charles 的破解。Charles 初始化设置Charles 的工作原理是将自身设置为系统的代理服务器来捕获所有的网络请求。所以使用 Charles ,我们必须设置 Charles 为系统的代理服务器。打开 Charles,当第一次启动的时候如果没有购买或者没有破解,会有倒计时,之后会看到软件的主界面,然后会请求你赋予它为系统代理的权限。点击授权会让你输入当前系统用户的密码。当然你也可以忽略或者拒绝该请求,然后等想要抓包的时候将它设置为系统的代理服务器。步骤:选择菜单中的“Proxy” -> “Mac OS X Proxy”。如下图:之后你的电脑上的任何网络请求都可以在 Charles 的请求面板中看到看看 Charles 的主界面图上红色圈1:这里代表所有网络请求的展示方式。分别名为 “Structure” 和 “Sequence”。Structure 将所有的网络请求按照域名划分并展示Sequence 将所有的网络请求按照时间排序并展示图上红色圈2:一些的网络请求设置比如 HTTPS 以及端口等信息都在这个菜单栏设置图上红色圈3:证书设置都在这里进行过滤网络请求由于 Charles 可以将电脑或者设置过的手机的所有网络请求捕获到,而且我们分析网络传输应该是针对某个特定的网络下的抓包分析,为了清楚明显地看到我们感兴趣的网络请求通常会用到 Charles 的“过滤网络请求的功能”。方法1:在 Charles 主面板的左侧所有网络请求的下方可以看到看到一个 ”Filter“ 输入栏,在这里你可以输入关键词来筛选出自己感兴趣的网络请求。比如我想分析的网络请求来自于”www.baidu.com" 下,你可以在下面输入"baidu"即可。方法2:在 Charles 菜单栏的顶部会看到 “Proxy” 的选项,点击菜单栏选择 “Proxy” -> “Recording Settings” 。选择 “include”。看到面板上面有一个 “Add” 按钮,点击后在弹出的面板里面设置好我们需要分析的网络请求的协议、主机名、端口、路径、参数,当然你也可以只设置一些主要的信息,比如协议和主机名的组合。方法3:一般打开 Charles 并设置好配置信息后(比如电脑本机或者设置过代理的手机)所有的网络请求都将在 Charles 的面板上显示,同时我们感兴趣的网络请求如果也在面板上显示的话,“Structure”模式下可以选中需要分析的网络请求,鼠标右击选择“Focus”。“Sequence”模式下可以在面板的网络请求显示面板的右下角看到一个Focus按钮,点击勾选后 Charles 只会显示你感兴趣的网络请求。截取HTTP/HTTPS数据截取 HTTP 请求Charles 的主要目的是抓取捕获网络请求,这里以 iPhone 的抓包为例讲解。Charles 的设置要截获 iPhone 的网络请求就需要为 Charles 开启代理功能。在菜单栏选择“Proxy” ->“Proxy Settings”。填写代理的端口号并将“Enable transparent HTTP proxying”勾选上。iPhone 上的设置在电脑“系统偏好设置”中心打开网络查看本机 IP 地址,打开手机“设置”->“无线局域网”,进入当前使用的网络,点击进入当前 WIFI 的详情页(可以看到当前 WIFI 的基本信息,包括子网掩码、端口、IP地址、路由器),在最下角可以看到“DNS”和“HTTP代理”2个section。我们点击“配置代理”,设置 HTTP 代理选中“手动”。服务器处填写电脑ip地址,端口写8888。设置好后,我们打开 iPhone 上的任意需要网络请求的应用,就可以看到 Charles 弹出请求的确认菜单,单击"Allow"按钮,即可完成设置。截取 HTTPS 请求如果你需要捕获 HTTPS 协议的网络请求,那么则需要安装 Charles 的 CA 证书。步骤如下;首先需要在 MAC 上安装证书。点击 Charles 顶部的菜单栏,选择 “Help” -> “SSL Proxying” -> “Install Charles Root Certificate”。在 keychain 处将新安装的证书设置为永久信任即使安装了 CA 证书,Charles 默认是不捕获 HTTPS 协议的网络请求,所以我们需要对某个主机下的网络请求抓包分析的话,选中该网络请求右击选中 “SSL Proxying Enabled”。这样就可以看到我们感兴趣的HTTPS 网络请求了。如果你需要捕获移动设备的 HTTPS 网络请求,则需要在移动设备上安装证书并作简单的设置选择 Charles 顶部菜单栏选择 “Help” ->“Install Charles Root Certificate on a Mobile Device or Remote Browser”。然后就可以看到 Charles 弹出的安装说明了。在手机设置好 Charles 代理的情况下,在手机浏览器输入 “chls.pro/ssl”。安装提示下载好CA证书。验证刚刚安装的 CA证书iPhone 打开设置 -> 通用 -> 关于本机 -> 证书信任设置 -> 开启开关在 Charles 菜单栏 Proxy -> SSL Proxying Setting -> 点击 Add 按钮 -> 在弹出的对对话框设置需要监听的 HTTPS 域(*:代表通配符)设置完毕,尽情抓取你想要的 HTTPS 网络请求吧。## 模拟弱网环境在平时开发的时候我们经常需要模拟弱网环境,并作弱网环境下的适配工作。Charles 为我们提供了这个服务。在 Charles 菜单栏选择 “Proxy” -> “Throttle Settings”。在弹出的面板上设置网络请求的参数(上行,下行带宽、利用率、可靠性等等信息)。如下图所示。如果你想对指定主机进行弱网环境下的测试,可以点击上图的“Add”按钮,在弹出的面板上设置协议、主机、端口来对指定的主机进行弱网设置。修改网络请求对于捕获的网络请求,我们经常需要修改网络请求的cookie、Headers、Url等信息。Charles 提供了对网络请求的编辑和重发功能。只需要选中需要修改编辑的网络请求,在对应的右上角看到有一个“钢笔”的按钮,点击后就可以对选中的网络请求进行编辑了,编辑好后可以在右下角看到 Execute 按钮。这样我们编辑后的网络请求就可以被执行了。修改服务器返回内容很多时候为了方便调试代码,我们会有这种需求,修改接口返回的数据节点或者内容、甚至是状态码。比如数据为空、数据异常、请求失败、多页数据的情况。 Charles 为我们提供了超实用的功能,“Map(Map Local、Map Remote)功能”、Rewrite功能、Breakpoints功能 ,都可以实现修改服务端返回数据的功能。但是有区别和适用场景:Map 功能适合长期地将某一请求重定向到另一个指定的网络地址或者本地 JSON 文件Rewrite 功能适合对网络请求进行一些正则替换Breakpoints 功能适合对网络请求进行一些临时性的修改(类似于我们开发的断点作用)Map 功能Map 功能分为 Map Local(将某个网络请求重定向到本地 JSON 文件) 和 Map Remote 功能(将网络请求重定向到另一个网络接口)。在 Charles 菜单栏选择 “Tools” -> “Map Remote” 或 “Map Local” 即可进入相应的功能模块。Map Remote 功能适合于切换线上到本地、测试服务到正式服务的场景。比如下图从正式服务切换到测试服务Map Local 功能我们需要填写重定向的原地址信息和本地目标文件。我们可以先将某个接口的响应内容保存下来(选择对应的网络请求,右击点击 Save Response )成为 data.json 文件。然后我们编辑里面的 status 、message、data 等信息为我们想要的目标映射文件。如下所示,我将一个网络请求的内容映射到我本地的一个 JSON 文件。之后这个请求的内容都从网络变为返回我本地的数据了。Map Local 可能会存在一个小缺陷,其返回的 HTTP Response Header 与正常的网络请求不一样,如果程序设置了校验 Header 信息,此时 Map Local 就会失败,解决办法是同时使用 Rewrite功能将相关的HTTP 头部信息 rewrite 成我们需要的信息Rewrite 功能Rewrite 适合对某个网络请求进行正则替换,以达到修改结果的目的。假如我的 App 的界面上的显示的功能模块及其点击事件是根据接口来完成的,我想实现替换功能模块的名称的目的。步骤:点击顶部菜单栏的“Tools” -> “Rewrite”。在弹出的面板上勾选 “Enable Rewrite”。点击左下角的 Add按钮,在右上角的 Name:处写好本次配置的名称(如果有多个 Rewrite,为了后期容易区分)。可以针对特定的网络请求进行 Rewrite。可以点击右上角 Location 面板下面的 Add按钮。在弹出的面板上设置网络请求配置信息。注意此时需要同时设置 Protocol、Port、Host、Path信息(我测试加了 Protocol、Host、Port这3个是无效的)然后对指定的 Type 和 Action 进行 Rewrite。Type 主要有 Add Header、Modify Header、Remove Header、Host、Path等等。Where 可以选择 Request 和 Response。指的是下面的修改是针对 Request 还是 Response完成设置后点击 Apply 按钮,即可生效。下次继续请求该网络,返回的内容就是我们刚刚设置的内容。比如当前的“政策法规”要变成“哈哈哈,我是假的政策法规”。这时候就可以使用 Rewrite 功能Breakpoints 功能Breakpoints 相比于其他几个修改网络请求的特点是只是针对当前的网络请求,Breakpoints 只存在于设置过的当前的网络请求,Charles 关闭后下次打开 Breakpoints 消失了。想要修改网络请求 Breakpoints 步骤最简单,跟我们调试工具里面设置的断点一样方便。对于我们设置了 Breakpoints 的网络请求, Charles 会在下次继续访问该请求的时候停止掉,就跟 debug 一样。此时我们可以 Edit Request,修改过 Request 之后点击右下角的 Execute 按钮。然后等到服务端返回的时候继续是断点状态,此时可以 Edit Response。步骤: 选中某个网络请求 -> 右击 -> 点击“Breakpoints”。如下图:对该接口设置了 Breakpoints。请求网络后 Edit Response,点击 execute 后服务端返回的结果就是我们编辑的内容了。服务器压力测试我们可以使用 Charles 的 Repeat 功能地对服务器进行并发访问进行压力测试。步骤:选中某个网络请求 -> 右击 -> Repeat Advanced -> 在弹出的面板里面设置总共的迭代次数(Iterations)、并发数(Concurrency) -> 点击“OK” 。开始执行可以看到以设置的并发数的规模,进行总共达设置的总共迭代次数的访问。(专业的压力测试工具:Load Runner)反向代理Charles 的反向代理功能允许我们将本地指定端口的请求映射到远程的另一个端口上。设置:点击顶部菜单栏 Proxy -> 点击 Reverse Proxies。如下所示,我将本地的 8080 端口映射到远程的 80 端口上,点击 OK 生效后,当我继续访问本地的 80 端口,实际返回的就是远程 80 端口的提供的内容了。解决与翻墙软件的冲突Charles 的工作原理是把自己设置为系统的代理服务器,但是我们开发者经常会利用 VPN 翻墙访问谷歌查找资料(这些翻墙软件的工作原理也是把自己设置成为系统的代理服务器),为了2者和平共处。我们可以在 Charles 的 External Proxy Settings 中将翻墙的代理端口等信息填写。同时我们需要关闭翻墙软件的自动设置,更改为“手动模式”。(使其不主动修改系统代理)总结Charles 功能强大、界面简洁,读完这篇文章并做出练习,相信你能很快掌握它,“工欲善其事,必先利其器” ,掌握了它,相信可以为你大大提高开发中调试网络的效率。Enjoy yourself ...

January 20, 2019 · 2 min · jiezi

第一章:python爬虫的基本流程

网络爬虫是什么?网络爬虫就是:请求网站并提取数据的自动化程序网络爬虫能做什么?网络爬虫被广泛用于互联网搜索引擎或其他类似网站,可以自动采集所有其能够访问到的页面内容,以获取或更新这些网站的内容和检索方式。网络爬虫还被用于爬取各个网站的数据,进行分析、预测近几年来,大量的企业和个人开始使用网络爬虫采集互联网的公开数据,进行数据分析,进一步达到商业目的。利用网络爬虫能从网上爬取什么数据?可以好不吹嘘的说,平时从浏览器浏览的所有数据都能被爬取下来。网络爬虫是否合法?上面说到了爬虫可以爬取任何数据,那么,爬取数据这种行为是否合法?目前关于爬取数据的法律还在建立和完善中,如果爬取的数据属于个人使用或者科研范畴,基本不存在什么问题;一旦要用于商业用途就得注意了,有可能会违法。互联网界对于网络爬虫也建立了一定的道德规范(Robots协议)来约束。这里具体看下Robots协议Robots协议规定各个搜索引擎哪些页面可以抓取,哪些页面不能抓取,Robots协议虽然没有被写入法律,但是每一个爬虫都应该遵守这项协议。下面是淘宝网的robots协议:从图中我们就可以发现淘宝网对百度的爬虫引擎做出了规定,然而百度也会遵守这些规定,不信你可以试试从百度是找不到淘宝里的商品信息的。python爬虫的基本流程Python爬虫的基本流程非常简单,主要可以分为三部分:(1)获取网页;(2)解析网页(提取数据);(3)存储数据。简单的介绍下这三部分:获取网页就是给一个网址发送请求,该网址会返回整个网页的数据。类似于在浏览器中输入网址并按回车键,然后可以看到网站的整个页面。解析网页就是从整个网页的数据中提取想要的数据。存储数据顾名思义就是把数据存储下来,我们可以存储在文本中,也可以存储到数据库中。基础爬虫的框架以及详细的运行流程基础爬虫框架主要包括五大模块,分别是爬虫调度器、URL管理器、HTML下载器、HTML解析器、数据存储器。这五大模块之间的关系如下图所示:下来我们来分析这五大模块之间的功能:爬虫调度器主要负责统筹其他四个模块的协调工作。URL管理器负责管理URL链接,维护已经爬取的URL集合和未爬取的URL集合, 提供获取新URL链接的接口。HTML下载器用于从URL管理器中获取未爬取的URL链接并下载HTML网页。HTML解析器用于从HTML下载器中获取已经下载的HTML网页,并从中解析出新 的URL链接交给URL管理器,解析出有效数据交给数据存储器。数据存储器用于将HTML解析器解析出来的数据通过文件或者数据库的形式存储起来。详细的运行流程如下图所示:

January 19, 2019 · 1 min · jiezi

大前端时代安全性如何做

之前在上家公司的时候做过一些爬虫的工作,也帮助爬虫工程师解决过一些问题。然后我写过一些文章发布到网上,之后有一些人就找我做一些爬虫的外包,内容大概是爬取小红书的用户数据和商品数据,但是我没做。我觉得对于国内的大数据公司没几家是有真正的大数据量,而是通过爬虫工程师团队不断的去各地爬取数据,因此不要以为我们的数据没价值,对于内容型的公司来说,数据是可信竞争力。那么我接下来想说的就是网络和数据的安全性问题。对于内容型的公司,数据的安全性很重要。对于内容公司来说,数据的重要性不言而喻。比如你一个做在线教育的平台,题目的数据很重要吧,但是被别人通过爬虫技术全部爬走了?如果核心竞争力都被拿走了,那就是凉凉。再比说有个独立开发者想抄袭你的产品,通过抓包和爬虫手段将你核心的数据拿走,然后短期内做个网站和 App,短期内成为你的劲敌。背景目前通过 App 中的 网页分析后,我们的数据安全性做的较差,有以下几个点存在问题:网站的数据通过最早期的前后端分离来实现。稍微学过 Web 前端的工程师都可以通过神器 Chrome 分析网站,进而爬取需要的数据。打开 「Network」就可以看到网站的所有网络请求了,哎呀,不小心我看到了什么?没错就是网站的接口信息都可以看到了。比如 “detail.json?itemId=141529859”。或者你的网站接口有些特殊的判断处理,将一些信息存储到 sessionStorage、cookie、localStorage 里面,有点前端经验的爬虫工程师心想”嘿嘿嘿,这不是在裸奔数据么“。或者有些参数是通过 JavaScript 临时通过函数生成的。问题不大,工程师也可以对网页元素进行查找,找到关键的 id、或者 css 类名,然后在 “Search“ 可以进行查找,找到对应的代码 JS 代码,点击查看代码,如果是早期前端开发模式那么代码就是裸奔的,跟开发者在自己的 IDE 里面看到的内容一样,有经验的爬虫就可以拿这个做事情,因此安全性问题亟待解决。想知道 Chrome 更多的调试使用技巧,看看这篇文章App 的数据即使采用了 HTTPS,但是对于专业的抓包工具也是可以直接拿到数据的,因此 App 的安全问题也可以做一些提高,具体的策略下文会讲到。想知道 Charles 的更多使用技巧,可以看看这篇文章爬虫手段目前爬虫技术都是从渲染好的 html 页面直接找到感兴趣的节点,然后获取对应的文本有些网站安全性做的好,比如列表页可能好获取,但是详情页就需要从列表页点击对应的 item,将 itemId 通过 form 表单提交,服务端生成对应的参数,然后重定向到详情页(重定向过来的地址后才带有详情页的参数 detailID),这个步骤就可以拦截掉一部分的爬虫开发者解决方案制定出Web 端反爬技术方案本人从这2个角度(网页所见非所得、查接口请求没用)出发,制定了下面的反爬方案。使用HTTPS 协议单位时间内限制掉请求次数过多,则封锁该账号前端技术限制 (接下来是核心技术)# 比如需要正确显示的数据为“19950220”1. 先按照自己需求利用相应的规则(数字乱序映射,比如正常的0对应还是0,但是乱序就是 0 <-> 1,1 <-> 9,3 <-> 8,…)制作自定义字体(ttf)2. 根据上面的乱序映射规律,求得到需要返回的数据 19950220 -> 177302203. 对于第一步得到的字符串,依次遍历每个字符,将每个字符根据按照线性变换(y=kx+b)。线性方程的系数和常数项是根据当前的日期计算得到的。比如当前的日期为“2018-07-24”,那么线性变换的 k 为 7,b 为 24。4. 然后将变换后的每个字符串用“3.1415926”拼接返回给接口调用者。(为什么是3.1415926,因为对数字伪造反爬,所以拼接的文本肯定是数字的话不太会引起研究者的注意,但是数字长度太短会误伤正常的数据,所以用所熟悉的 )1773 -&gt; “1*7+24” + “3.1415926” + “7*7+24” + “3.1415926” + “7*7+24” + “3.1415926” + “3*7+24” -&gt; 313.1415926733.1415926733.14159264502 -&gt; "0*7+24" + "3.1415926" + "2*7+24" -&gt; 243.14159263820 -&gt; "2*7+24" + "3.1415926" + "0*7+24" -&gt; 383.141592624# 前端拿到数据后再解密,解密后根据自定义的字体 Render 页面1. 先将拿到的字符串按照“3.1415926”拆分为数组2. 对数组的每1个数据,按照“线性变换”(y=kx+b,k和b同样按照当前的日期求解得到),逆向求解到原本的值。3. 将步骤2的的到的数据依次拼接,再根据 ttf 文件 Render 页面上。后端需要根据上一步设计的协议将数据进行加密处理下面以 Node.js 为例讲解后端需要做的事情首先后端设置接口路由获取路由后面的参数根据业务需要根据 SQL 语句生成对应的数据。如果是数字部分,则需要按照上面约定的方法加以转换。将生成数据转换成 JSON 返回给调用者// jsonvar JoinOparatorSymbol = “3.1415926”;function encode(rawData, ruleType) { if (!isNotEmptyStr(rawData)) { return “”; } var date = new Date(); var year = date.getFullYear(); var month = date.getMonth() + 1; var day = date.getDate(); var encodeData = “”; for (var index = 0; index < rawData.length; index++) { var datacomponent = rawData[index]; if (!isNaN(datacomponent)) { if (ruleType < 3) { var currentNumber = rawDataMap(String(datacomponent), ruleType); encodeData += (currentNumber * month + day) + JoinOparatorSymbol; } else if (ruleType == 4) { encodeData += rawDataMap(String(datacomponent), ruleType); } else { encodeData += rawDataMap(String(datacomponent), ruleType) + JoinOparatorSymbol; } } else if (ruleType == 4) { encodeData += rawDataMap(String(datacomponent), ruleType); } } if (encodeData.length >= JoinOparatorSymbol.length) { var lastTwoString = encodeData.substring(encodeData.length - JoinOparatorSymbol.length, encodeData.length); if (lastTwoString == JoinOparatorSymbol) { encodeData = encodeData.substring(0, encodeData.length - JoinOparatorSymbol.length); } }//字体映射处理function rawDataMap(rawData, ruleType) { if (!isNotEmptyStr(rawData) || !isNotEmptyStr(ruleType)) { return; } var mapData; var rawNumber = parseInt(rawData); var ruleTypeNumber = parseInt(ruleType); if (!isNaN(rawData)) { lastNumberCategory = ruleTypeNumber; //字体文件1下的数据加密规则 if (ruleTypeNumber == 1) { if (rawNumber == 1) { mapData = 1; } else if (rawNumber == 2) { mapData = 2; } else if (rawNumber == 3) { mapData = 4; } else if (rawNumber == 4) { mapData = 5; } else if (rawNumber == 5) { mapData = 3; } else if (rawNumber == 6) { mapData = 8; } else if (rawNumber == 7) { mapData = 6; } else if (rawNumber == 8) { mapData = 9; } else if (rawNumber == 9) { mapData = 7; } else if (rawNumber == 0) { mapData = 0; } } //字体文件2下的数据加密规则 else if (ruleTypeNumber == 0) { if (rawNumber == 1) { mapData = 4; } else if (rawNumber == 2) { mapData = 2; } else if (rawNumber == 3) { mapData = 3; } else if (rawNumber == 4) { mapData = 1; } else if (rawNumber == 5) { mapData = 8; } else if (rawNumber == 6) { mapData = 5; } else if (rawNumber == 7) { mapData = 6; } else if (rawNumber == 8) { mapData = 7; } else if (rawNumber == 9) { mapData = 9; } else if (rawNumber == 0) { mapData = 0; } } //字体文件3下的数据加密规则 else if (ruleTypeNumber == 2) { if (rawNumber == 1) { mapData = 6; } else if (rawNumber == 2) { mapData = 2; } else if (rawNumber == 3) { mapData = 1; } else if (rawNumber == 4) { mapData = 3; } else if (rawNumber == 5) { mapData = 4; } else if (rawNumber == 6) { mapData = 8; } else if (rawNumber == 7) { mapData = 3; } else if (rawNumber == 8) { mapData = 7; } else if (rawNumber == 9) { mapData = 9; } else if (rawNumber == 0) { mapData = 0; } } else if (ruleTypeNumber == 3) { if (rawNumber == 1) { mapData = “&#xefab;”; } else if (rawNumber == 2) { mapData = “&#xeba3;”; } else if (rawNumber == 3) { mapData = “&#xecfa;”; } else if (rawNumber == 4) { mapData = “&#xedfd;”; } else if (rawNumber == 5) { mapData = “&#xeffa;”; } else if (rawNumber == 6) { mapData = “&#xef3a;”; } else if (rawNumber == 7) { mapData = “&#xe6f5;”; } else if (rawNumber == 8) { mapData = “&#xecb2;”; } else if (rawNumber == 9) { mapData = “&#xe8ae;”; } else if (rawNumber == 0) { mapData = “&#xe1f2;”; } } else{ mapData = rawNumber; } } else if (ruleTypeNumber == 4) { var sources = [“年”, “万”, “业”, “人”, “信”, “元”, “千”, “司”, “州”, “资”, “造”, “钱”]; //判断字符串为汉字 if (/^[\u4e00-\u9fa5]$/.test(rawData)) { if (sources.indexOf(rawData) > -1) { var currentChineseHexcod = rawData.charCodeAt(0).toString(16); var lastCompoent; var mapComponetnt; var numbers = [“0”, “1”, “2”, “3”, “4”, “5”, “6”, “7”, “8”, “9”]; var characters = [“a”, “b”, “c”, “d”, “e”, “f”, “g”, “h”, “h”, “i”, “j”, “k”, “l”, “m”, “n”, “o”, “p”, “q”, “r”, “s”, “t”, “u”, “v”, “w”, “x”, “y”, “z”]; if (currentChineseHexcod.length == 4) { lastCompoent = currentChineseHexcod.substr(3, 1); var locationInComponents = 0; if (/[0-9]/.test(lastCompoent)) { locationInComponents = numbers.indexOf(lastCompoent); mapComponetnt = numbers[(locationInComponents + 1) % 10]; } else if (/[a-z]/.test(lastCompoent)) { locationInComponents = characters.indexOf(lastCompoent); mapComponetnt = characters[(locationInComponents + 1) % 26]; } mapData = “&#x” + currentChineseHexcod.substr(0, 3) + mapComponetnt + “;”; } } else { mapData = rawData; } } else if (/[0-9]/.test(rawData)) { mapData = rawDataMap(rawData, 2); } else { mapData = rawData; } } return mapData;}//apimodule.exports = { “GET /api/products”: async (ctx, next) => { ctx.response.type = “application/json”; ctx.response.body = { products: products }; }, “GET /api/solution1”: async (ctx, next) => { try { var data = fs.readFileSync(pathname, “utf-8”); ruleJson = JSON.parse(data); rule = ruleJson.data.rule; } catch (error) { console.log(“fail: " + error); } var data = { code: 200, message: “success”, data: { name: “@杭城小刘”, year: LBPEncode(“1995”, rule), month: LBPEncode(“02”, rule), day: LBPEncode(“20”, rule), analysis : rule } } ctx.set(“Access-Control-Allow-Origin”, “”); ctx.response.type = “application/json”; ctx.response.body = data; }, “GET /api/solution2”: async (ctx, next) => { try { var data = fs.readFileSync(pathname, “utf-8”); ruleJson = JSON.parse(data); rule = ruleJson.data.rule; } catch (error) { console.log(“fail: " + error); } var data = { code: 200, message: “success”, data: { name: LBPEncode(“建造师”,rule), birthday: LBPEncode(“1995年02月20日”,rule), company: LBPEncode(“中天公司”,rule), address: LBPEncode(“浙江省杭州市拱墅区石祥路”,rule), bidprice: LBPEncode(“2万元”,rule), negative: LBPEncode(“2018年办事效率太高、负面基本没有”,rule), title: LBPEncode(“建造师”,rule), honor: LBPEncode(“最佳奖”,rule), analysis : rule } } ctx.set(“Access-Control-Allow-Origin”, “*”); ctx.response.type = “application/json”; ctx.response.body = data; }, “POST /api/products”: async (ctx, next) => { var p = { name: ctx.request.body.name, price: ctx.request.body.price }; products.push(p); ctx.response.type = “application/json”; ctx.response.body = p; }};//路由const fs = require(“fs”);function addMapping(router, mapping){ for(var url in mapping){ if (url.startsWith(“GET”)) { var path = url.substring(4); router.get(path,mapping[url]); console.log(Register URL mapping: GET: ${path}); }else if (url.startsWith(‘POST ‘)) { var path = url.substring(5); router.post(path, mapping[url]); console.log(Register URL mapping: POST ${path}); } else if (url.startsWith(‘PUT ‘)) { var path = url.substring(4); router.put(path, mapping[url]); console.log(Register URL mapping: PUT ${path}); } else if (url.startsWith(‘DELETE ‘)) { var path = url.substring(7); router.del(path, mapping[url]); console.log(Register URL mapping: DELETE ${path}); } else { console.log(Invalid URL: ${url}); } }}function addControllers(router, dir){ fs.readdirSync(__dirname + “/” + dir).filter( (f) => { return f.endsWith(".js”); }).forEach( (f) => { console.log(Process controllers:${f}...); let mapping = require(__dirname + “/” + dir + “/” + f); addMapping(router,mapping); });}module.exports = function(dir){ let controllers = dir || “controller”; let router = require(“koa-router”)(); addControllers(router,controllers); return router.routes();};前端根据服务端返回的数据逆向解密$("#year”).html(getRawData(data.year,log));// util.jsvar JoinOparatorSymbol = “3.1415926”;function isNotEmptyStr($str) { if (String($str) == "” || $str == undefined || $str == null || $str == “null”) { return false; } return true;}function getRawData($json,analisys) { $json = $json.toString(); if (!isNotEmptyStr($json)) { return; } var date= new Date(); var year = date.getFullYear(); var month = date.getMonth() + 1; var day = date.getDate(); var datacomponents = $json.split(JoinOparatorSymbol); var orginalMessage = “”; for(var index = 0;index < datacomponents.length;index++){ var datacomponent = datacomponents[index]; if (!isNaN(datacomponent) && analisys < 3){ var currentNumber = parseInt(datacomponent); orginalMessage += (currentNumber - day)/month; } else if(analisys == 3){ orginalMessage += datacomponent; } else{ //其他情况待续,本 Demo 根据本人在研究反爬方面的技术并实践后持续更新 } } return orginalMessage;}比如后端返回的是323.14743.14743.1446,根据我们约定的算法,可以的到结果为1773根据 ttf 文件 Render 页面上面计算的到的1773,然后根据ttf文件,页面看到的就是1995然后为了防止爬虫人员查看 JS 研究问题,所以对 JS 的文件进行了加密处理。如果你的技术栈是 Vue 、React 等,webpack 为你提供了 JS 加密的插件,也很方便处理JS混淆工具个人觉得这种方式还不是很安全。于是想到了各种方案的组合拳。比如反爬升级版个人觉得如果一个前端经验丰富的爬虫开发者来说,上面的方案可能还是会存在被破解的可能,所以在之前的基础上做了升级版本组合拳1: 字体文件不要固定,虽然请求的链接是同一个,但是根据当前的时间戳的最后一个数字取模,比如 Demo 中对4取模,有4种值 0、1、2、3。这4种值对应不同的字体文件,所以当爬虫绞尽脑汁爬到1种情况下的字体时,没想到再次请求,字体文件的规则变掉了 ????组合拳2: 前面的规则是字体问题乱序,但是只是数字匹配打乱掉。比如 1 -> 4, 5 -> 8。接下来的套路就是每个数字对应一个 unicode 码 ,然后制作自己需要的字体,可以是 .ttf、.woff 等等。这几种组合拳打下来。对于一般的爬虫就放弃了。反爬手段再升级上面说的方法主要是针对数字做的反爬手段,如果要对汉字进行反爬怎么办?接下来提供几种方案方案1: 对于你站点频率最高的词云,做一个汉字映射,也就是自定义字体文件,步骤跟数字一样。先将常用的汉字生成对应的 ttf 文件;根据下面提供的链接,将 ttf 文件转换为 svg 文件,然后在下面的“字体映射”链接点进去的网站上面选择前面生成的 svg 文件,将svg文件里面的每个汉字做个映射,也就是将汉字专为 unicode 码(注意这里的 unicode 码不要去在线直接生成,因为直接生成的东西也就是有规律的。我给的做法是先用网站生成,然后将得到的结果做个简单的变化,比如将“e342”转换为 “e231”);然后接口返回的数据按照我们的这个字体文件的规则反过去映射出来。方案2: 将网站的重要字体,将 html 部分生成图片,这样子爬虫要识别到需要的内容成本就很高了,需要用到 OCR。效率也很低。所以可以拦截掉一部分的爬虫方案3: 看到携程的技术分享“反爬的最高境界就是 Canvas 的指纹,原理是不同的机器不同的硬件对于 Canvas 画出的图总是存在像素级别的误差,因此我们判断当对于访问来说大量的 canvas 的指纹一致的话,则认为是爬虫,则可以封掉它”。本人将方案1实现到 Demo 中了。关键步骤先根据你们的产品找到常用的关键词,生成词云根据词云,将每个字生成对应的 unicode 码将词云包括的汉字做成一个字体库将字体库 .ttf 做成 svg 格式,然后上传到 icomoon 制作自定义的字体,但是有规则,比如 “年” 对应的 unicode 码是 “u5e74” ,但是我们需要做一个 恺撒加密 ,比如我们设置 偏移量 为1,那么经过恺撒加密 “年”对应的 unicode 码是“u5e75” 。利用这种规则制作我们需要的字体库在每次调用接口的时候服务端做的事情是:服务端封装某个方法,将数据经过方法判断是不是在词云中,如果是词云中的字符,利用规则(找到汉字对应的 unicode 码,再根据凯撒加密,设置对应的偏移量,Demo 中为1,将每个汉字加密处理)加密处理后返回数据客户端做的事情:先引入我们前面制作好的汉字字体库调用接口拿到数据,显示到对应的 Dom 节点上如果是汉字文本,我们将对应节点的 css 类设置成汉字类,该类对应的 font-family 是我们上面引入的汉字字体库//style.css@font-face { font-family: “NumberFont”; src: url(‘http://127.0.0.1:8080/Util/analysis’); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;}@font-face { font-family: “CharacterFont”; src: url(‘http://127.0.0.1:8080/Util/map’); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;}h2 { font-family: “NumberFont”;}h3,a{ font-family: “CharacterFont”;}传送门字体制作的步骤、ttf转svg、字体映射规则实现的效果页面上看到的数据跟审查元素看到的结果不一致去查看接口数据跟审核元素和界面看到的三者不一致页面每次刷新之前得出的结果更不一致对于数字和汉字的处理手段都不一致这几种组合拳打下来。对于一般的爬虫就放弃了。前面的 ttf 转 svg 网站当 ttf 文件太大会限制转换,让你购买,下面贴出个新的链接。ttf转svgDemo 地址运行步骤//客户端。先查看本机 ip 在 Demo/Spider-develop/Solution/Solution1.js 和 Demo/Spider-develop/Solution/Solution2.js 里面将接口地址修改为本机 ip$ cd Demo$ lsREST Spider-release file-Server.jsSpider-develop Util rule.json$ node file-Server.js Server is runnig at http://127.0.0.1:8080///服务端 先安装依赖$ cd REST/$ npm install$ node app.js App 端安全的解决方案目前 App 的网络通信基本都是用 HTTPS 的服务,但是随便一个抓包工具都是可以看到 HTTPS 接口的详细数据,为了做到防止抓包和无法模拟接口的情况,我们采取以下措施:中间人盗用数据,我们可以采取 HTTPS 证书的双向认证,这样子实现的效果就是中间人在开启抓包软件分析 App 的网络请求的时候,网络会自动断掉,无法查看分析请求的情况对于防止用户模仿我们的请求再次发起请求,我们可以采用 「防重放策略」,用户再也无法模仿我们的请求,再次去获取数据了。对于 App 内的 H5 资源,反爬虫方案可以采用上面的解决方案,H5 内部的网络请求可以通过 Hybrid 层让 Native 的能力去完成网络请求,完成之后将数据回调给 JS。这么做的目的是往往我们的 Native 层有完善的账号体系和网络层以及良好的安全策略、鉴权体系等等。后期会讨论 App 安全性的更深层次玩法,比如从逆向的角度出发如何保护 App 的安全性。提前给出一篇逆向安全方面的文章关于 Hybrid 的更多内容,可以看看这篇文章 Awesome Hybrid比如 JS 需要发起一个网络请求,那么按照上面将网络请求让 Native 去完成,然后回调给 JSJS 端代码var requestObject = { url: arg.Api + “SearchInfo/getLawsInfo”, params: requestparams, Hybrid_Request_Method: 0};requestHybrid({ tagname: ‘NativeRequest’, param: requestObject, encryption: 1, callback: function (data) { renderUI(data); }})Native 代码(iOS为例)[self.bridge registerHandler:@“NativeRequest” handler:^(id data, WVJBResponseCallback responseCallback) { NSAssert([data isKindOfClass:[NSDictionary class]], @“H5 端不按套路”); if ([data isKindOfClass:[NSDictionary class]]) { NSDictionary *dict = (NSDictionary *)data; RequestModel *requestModel = [RequestModel yy_modelWithJSON:dict]; NSAssert( (requestModel.Hybrid_Request_Method == Hybrid_Request_Method_Post) || (requestModel.Hybrid_Request_Method == Hybrid_Request_Method_Get ), @“H5 端不按套路”); [HybridRequest requestWithNative:requestModel hybridRequestSuccess:^(id responseObject) { NSDictionary *json = [NSJSONSerialization JSONObjectWithData:responseObject options:NSJSONReadingMutableLeaves error:nil]; responseCallback([self convertToJsonData:@{@“success”:@“1”,@“data”:json}]); } hybridRequestfail:^{ LBPLog(@“H5 call Native`s request failed”); responseCallback([self convertToJsonData:@{@“success”:@“0”,@“data”:@""}]); }]; }}];以上是第一阶段的安全性总结,后期应该会更新(App逆向、防重放、服务端等)。 ...

January 15, 2019 · 8 min · jiezi

大众点评爬虫

大众点评爬虫可用api大众点评爬虫、API,可以进行单独城市、单独地区、单独商铺的爬取、搜索、多类型地区搜索、信息获取、提供MongoDB数据库存储支持,可以进行点评文本解密的爬取、存储。源码:地址可用于大众点评网页版目前可用:获取大众点评当前可以查询查看到店铺的所有已激活城市信息获取大众点评里所有省市直辖市的provinceId以及地区areaId根据给定的中文城市、地区名称来获取其大众点评首页链接通过id获取地区名称通过id获取地区内的所有子地区信息获取某个城市的 热搜关键词获取某个城市的 当前可见有效店铺分类获取某个城市的 当前可见有效的辖区信息,包含子地区获取某个城市的 某个关键词相关的搜索结果和结果数(单线程与多线程)搜索某个城市关于某个关键词的某个分类、子地区、排序方式的相关店铺并支持MongoDB存储和本地文件存储获取某个商铺的评分、星级、地址、电话、点评数、人均消费、点评标签、点评种类等获取某个商铺的加密点评信息,支持条数设定环境使用环境:win 7 64bitspycharmpython3第三方库:bs4 >=0.0.1lxml >=4.2.5pymongo >=3.7.1requests >=2.19.1使用前因为大众点评的反爬措施,需要设置IP代理以及随机切换可用的User-Agent来进行大量数据的爬取。IP代理(Proxy)如何设置?config.py中有详细的各个代理设置注释,建议使用PROXY_POOL进行IP代理,可以使用一次一个的接口来获取代理IP。UA池user-agent可以使用settings.py中的UA池,如果你有更多的可用UA,可以自己加进去或者替换掉。用户cookie如果你需要爬取加密的商铺点评数据(页数>1),则需要添加点评用户的登陆cookie到config.py的COOKIE中。具体内容为一个字符串,如:’lxsdk_cuid=1681d897b62c8;hc.v=ff4f63f6;thirdtoken=c9792’之类的。可以在浏览器调试界面获得。数据库存储(MongoDB)与本地存储如果需要存储搜索的店铺数据,则需要到config.py中设置MongoDB的数据库设置(本地存储可以在城市搜索api中设置保存本地的路径。),其中’database’为默认使用的数据库,‘records’为记录抓取的数据表名,‘searchDB’为搜索结果存放的数据库其他相关的配置可以参见config.py 中的设置注解使用获取大众点评当前可以查询查看到店铺的所有已激活城市信息from dianping import DianPingdp = DianPing()cities = dp.active_cities返回结果 cities 为大众点评全国可以显示搜索到店铺的激活城市列表:[ { “activeCity”: true, “appHotLevel”: 1, “cityAbbrCode”: “BJ”, “cityAreaCode”: “010”, “cityEnName”: “beijing”, “cityId”: 2, “cityLevel”: 1, “cityName”: “北京”, “cityOrderId”: 406, “cityPyName”: “beijing”, “directURL”: “”, “gLat”: 39.904667, “gLng”: 116.408198, “overseasCity”: false, “parentCityId”: 0, “provinceId”: 1, “scenery”: false, “tuanGouFlag”: 1 }, …]获取大众点评里所有省市直辖市的provinceId以及地区areaIdfrom dianping import DianPingdp = DianPing()provinces = dp.provinces返回结果 provinces 为全国的省、直辖市的ID信息:{ “北京”: { “areaId”: 1, “provinceId”: “1” }, “天津”: { “areaId”: 1, “provinceId”: “2” }, “河北”: { “areaId”: 1, “provinceId”: “3” }, “山西”: { “areaId”: 1, “provinceId”: “4” }, …}根据给定的中文城市、地区名称来获取其大众点评首页链接from city import Citybeijing = City(‘北京’)url = beijing.url返回结果 url 为北京市的大众点评首页:http://www.dianping.com/beijing获取某个城市的 当前可见有效的辖区信息,包含子地区from city import Citybeijing = City(‘北京’)beijing.get()locations = beijing.locations返回结果 locations 为当前城市的所有子地区信息:[ { “text”: “海淀区”, “value”: “17”, “children”: [ { “text”: “双榆树”, “value”: “2587”, “children”: [ { “text”: “HQ尚客百货”, “value”: “6975” }, { “text”: “当代商城”, “value”: “2622” }, { “text”: “华星影城”, “value”: “2665” }, { “text”: “双安商场”, “value”: “2720” } ] }, … ] }, …]通过id获取城市的某个地区名称#id 必须为城市中某个地区的idfrom util.city import find_region_by_idfrom city import Citybeijing = City(‘北京’)beijing.get()someplace = find_region_by_id(6975,beijing.locations)返回结果 someplace 为北京地区的对应id的子地区名称:# ID 6975 对应的地区HQ尚客百货通过id获取地区内的所有子地区信息from util.city import find_children_regionsfrom city import Citybeijing = City(‘北京’)beijing.get()sub_regions = find_children_regions(5956,beijing.locations)返回结果 sub_regions 为当前id下的所有子地区id列表:# id 在城市的子地区列表中,但其未有子地区,返回:False# id 在城市的子地区列表中, 如果其有子地区的话则返回子地区id列表:[‘6008’]# id 不在城市的子地区列表中,则返回城市的一级子地区:[‘17’, ‘5951’, ‘328’, ‘15’, ‘5952’, ‘14’, ‘5950’, ‘9158’, ‘16’, ‘20’, ‘9157’]获取某个城市的 热搜关键词from city import Citybeijing = City(‘北京’)beijing.get()hot = beijing.hot返回结果 hot 为当前城市“北京”的热搜词汇列表(包含其所属分类id等信息):[ { “subtag”: “3”, “location”: “7”, “maincategoryids”: “35,60”, “datatype”: “3002”, “id”: “786881”, “suggestkeyword”: “温泉” }, { “subtag”: “18”, “location”: “8”, “maincategoryids”: “10”, “datatype”: “3002”, “id”: “692874”, “suggestkeyword”: “烤鸭” }, …]获取某个城市的 当前可见有效店铺分类from city import Citybeijing = City(‘北京’)beijing.get()category = beijing.category返回结果 category 为该城市所有的店铺分类信息列表:[ { “text”: “酒吧”, “value”: “133”, “children”: [ { “text”: “清吧”, “value”: “33950” }, { “text”: “Live House”, “value”: “33951” }, { “text”: “夜店”, “value”: “2951” } ] }, { “text”: “茶馆”, “value”: “134” }, …]获取某个城市的 某个关键词相关的搜索结果和结果数from city import Citybeijing = City(‘北京’)beijing.get()relative = beijing.get_relative(‘健身’)返回结果 relative 为北京市关键词“健身”相关的搜索词汇以及其对应结果数:{ “良子健身 京粮大厦店”: “1”, “健身游泳瑜伽综合性会所”: “1”, “Hey Heroes!私教健身工作室 泛悦坊店”: “1”, “ULife悦体健身 五棵松店”: “1”, “U-Vista优维斯塔健身工作室 金融街旗舰店”: “1”, “健身房24小时”: “161”, “健身体验卡”: “1”, “锐健身”: “29”, “Hot Fitness 热健身工作室 霍营店”: “1”, “锻造健身ForgingFitness国际私教工作室”: “1”}(单线程与多线程)搜索某个城市关于某个关键词的某个分类、子地区、排序方式的相关店铺并支持MongoDB存储和本地文件存储单线程搜索下载相关店铺下例为搜索下载北京市“海淀区”店铺分类为“运动健身”的“有团购”的与“器材”相关的所有店铺,搜索下载结果“按人气排序”,save表示是否保存进MongoDB数据库,details表示是否抓取店铺的详细信息。具体参数可见search函数注释。from city import Citybeijing = City(‘北京’)beijing.get()results = beijing.search(‘器材’,category=‘运动健身’,location=‘海淀区’,filter=‘有团购’,sort=‘按人气排序’,save=True,details=True)返回结果 results 为搜索到的相关店铺,具体内容,单个店铺的MongoDB数据库显示:{ “_id” : ObjectId(“5c3c88f265b2fd3134266c7b”), “店名” : “优享健身(金源店)”, “星级” : “四星商户”, “注册时间” : “2017-07-25T23:19:00”, “地址” : “ 远大路世纪金源燕莎B1层卜蜂莲花超市内南侧家电区”, “人均” : 3526, “预订” : false, “分店url” : “http://www.dianping.com/brands/b93357498s45g45", “商铺图片” : “http://vfile.meituan.net/joymerchant/-1945301364589883676-23601423-1525363931678.jpg", “商铺标签” : “健身房”, “纬度” : 39.9573, “经度” : 116.28518, “电话” : [ “010-57159188” ], “店铺ID” : 93357498, “会员卡ID” : 0, “地区” : [ “远大路” ], “expand” : 0, “poi” : “HEHHURZVVGIDGF”, “promoId” : 0, “shopDealId” : 27807431, “shopPower” : 40, “hasSceneryOrder” : false, “点评数” : “118”, “点评标签” : [ “环境优雅(43)”, “服务热情(17)”, “设施很赞(15)”, “教练很棒(14)”, “器械齐全(7)”, “体验很棒(7)”, “干净卫生(4)”, “高大上(4)” ], “点评类别” : { “图片” : “55”, “好评” : “85”, “中评” : “7”, “差评” : “26” }, “评分” : { “设施” : “8.3”, “环境” : “8.2”, “服务” : “7.6” }}多线程搜索下载相关店铺多线程搜索与单线程搜索流程一致,只是搜索线程多开了而已,线程数为搜索结果的页数,最多为50个(大众点评目前单页最多个数)。启用多线程的话,由于使用代理IP,有可能同时获取的代理多个线程都是同一个,所以在config.py加入了RANDOM_INTERVAL(随机等待间隔)防止多个线程使用同一个代理被封。获取某个商铺的评分、星级、地址、电话、点评数、人均消费、点评标签、点评种类等下例以获取id为507576的店铺信息为例from shop import Shopstore = Shop(‘507576’)store.get()#店铺名name = store.name#店铺星级,50为五星,40为四星,35为三星半stars = store.stars#地址address = store.address#联系方式phone = store.phone#点评数reviews = store.reviews#人均消费average = store.average#顾客评分scores = store.scores#点评种类及数量comment_kinds = store.comment_kinds#点评标签及数量review_tags = store.review_tags返回结果 :‘满福楼’‘50’‘朝阳门外大街8号蓝岛大厦东区六层(东大桥铁站D2出口)‘‘‘64030992 64053088’‘18821’‘128’{‘口味’: ‘9.1’, ‘环境’: ‘9.1’, ‘服务’: ‘9.1’}{‘图片’: ‘3513’, ‘好评’: ‘17632’, ‘中评’: ‘355’, ‘差评’: ‘95’}[‘回头客(404)’, ‘干净卫生(253)’, ‘上菜快(106)’, ‘停车方便(59)’, ‘夜景赞(5)’, ‘请客(161)’, ‘朋友聚餐(93)’, ‘家庭聚餐(44)’, ‘现做现卖(22)’, ‘下午茶(9)’]获取下载保存某个商铺的加密点评信息,支持条数设定获取点评需要使用cookie,具体使用参见“使用前”,默认保存数据进入数据库,具体的参数详情参见get_reviews函数注释。下例以获取店铺id为 507576 的商铺以第2页为起点的 300条加密点评为例:from comment import Commentsfrom dbhelper import Databasefrom config import MongoDBdianpingDB = Database(MongoDB)target_shop = Comments(‘50576’,db=dianpingDB)target_shop.get()target_shop.get_reviews(tname=‘保存数据表名’,count=300,frompage=2)结果已经被存储在数据库中,MongoDB数据库中的单条点评数据内容具体为:{ “_id” : ObjectId(“5c3c914e65b2fd3384558c69”), “点评ID” : “496225994”, “点评链接” : “http://www.dianping.com/review/496225994", “点评用户” : “好想吃好”, “用户ID” : “781396343”, “用户主页” : “http://www.dianping.com/member/781396343", “用户头像” : “https://p0.meituan.net/userheadpicbackend/83fe454da66682fcbd43aed7e716f7c5104831.jpg", “用户等级” : “lv5”, “VIP用户” : true, “点评商铺” : “满福楼”, “商铺ID” : “507576”, “点评星级” : “50”, “用户评价” : { “口味” : “非常好”, “环境” : “非常好”, “服务” : “非常好” }, “点评时间” : “2019-01-13 21:17”, “点评图片” : [ “http://qcloud.dpfile.com/pc/chB7IwRZcpIwgAZu6ZkIE1Ts_aTNOyiGGbmIGbs4RjCN3JfGN-IioWlr9osF8hImjoJrvItByyS4HHaWdXyO_DrXIaWutJls2xCVbatkhjUNNiIYVnHvzugZCuBITtvjski7YaLlHpkrQUr5euoQrg.jpg”, “http://qcloud.dpfile.com/pc/zGhlhFul5cqBQs7e5Vz8vKGz21xv2xOlIEIwy7kf50p-NpRbr0UwQ7niAIDwKWOCjoJrvItByyS4HHaWdXyO_DrXIaWutJls2xCVbatkhjUNNiIYVnHvzugZCuBITtvjski7YaLlHpkrQUr5euoQrg.jpg”, “http://qcloud.dpfile.com/pc/Ve_jDe47G9v8Da_Hsy9MpZqCs0Fb67Yq6j2rkpqgN7H0kAawvhDXOXBa2ZnI1FIWjoJrvItByyS4HHaWdXyO_DrXIaWutJls2xCVbatkhjUNNiIYVnHvzugZCuBITtvjski7YaLlHpkrQUr5euoQrg.jpg”, “http://qcloud.dpfile.com/pc/StJvWhLl7MjtPi7VbNfYIR2I1IS9esxPO21bqfDKVaPygfipf3l2ctLCNDL5jbj1joJrvItByyS4HHaWdXyO_DrXIaWutJls2xCVbatkhjUNNiIYVnHvzugZCuBITtvjski7YaLlHpkrQUr5euoQrg.jpg”, “http://qcloud.dpfile.com/pc/7UmBsS10yRloypGc2Pp1pdlmdKP2hDzRhWT5wZLgI-JbnpM9T49yMmnt4yPiPxuwjoJrvItByyS4HHaWdXyO_DrXIaWutJls2xCVbatkhjUNNiIYVnHvzugZCuBITtvjski7YaLlHpkrQUr5euoQrg.jpg”, “http://qcloud.dpfile.com/pc/sI_CeStxWNFGJcOTa1bhmxOKkLGBEHBmXGaXkST7jk8t-HQzlOY35ADlO6UZvd_rjoJrvItByyS4HHaWdXyO_DrXIaWutJls2xCVbatkhjUNNiIYVnHvzugZCuBITtvjski7YaLlHpkrQUr5euoQrg.jpg”, “http://qcloud.dpfile.com/pc/fXFthqUVtnH64f56FytXNTHCV_h2ItUd8n-LqQllEf8Ho8itsBkcmVncTI8kIYxjjoJrvItByyS4HHaWdXyO_DrXIaWutJls2xCVbatkhjUNNiIYVnHvzugZCuBITtvjski7YaLlHpkrQUr5euoQrg.jpg”, “http://qcloud.dpfile.com/pc/7DrPJ4wMkZMQJQMwzrV_htFX28QHwS538qf9O7X1Hx0i8AgtQV76cj-_sKD6JTPFjoJrvItByyS4HHaWdXyO_DrXIaWutJls2xCVbatkhjUNNiIYVnHvzugZCuBITtvjski7YaLlHpkrQUr5euoQrg.jpg”, “http://qcloud.dpfile.com/pc/FZy3uddbXxAZkk2-J8EI7GFutnl-xTc7gEdOn8IsUFQrvkHyMac7eaNrOOmvIgmcjoJrvItByyS4HHaWdXyO_DrXIaWutJls2xCVbatkhjUNNiIYVnHvzugZCuBITtvjski7YaLlHpkrQUr5euoQrg.jpg”, “http://qcloud.dpfile.com/pc/px379Hd9MRRqOF6opKanBs8QEGc7UK5pjkSHtQoP9BrbMF6JuBDWN8BUF4VB5oV5joJrvItByyS4HHaWdXyO_DrXIaWutJls2xCVbatkhjUNNiIYVnHvzugZCuBITtvjski7YaLlHpkrQUr5euoQrg.jpg”, “http://qcloud.dpfile.com/pc/xwUMFgIHr8Uemd33o3rQMg6ktHU-4BMTzvInDCRMhkhXBw3IHLzmZnDdPoSzfFepjoJrvItByyS4HHaWdXyO_DrXIaWutJls2xCVbatkhjUNNiIYVnHvzugZCuBITtvjski7YaLlHpkrQUr5euoQrg.jpg” ], “评论事件” : { “赞” : “4” }, “点评内容” : “家里的抓到300元代金券,今天到店品尝美味。乘直梯到六楼,进店入眼就是放着小铜锅的餐桌,才十一点多点儿大厅就几乎满员。仨人按大众点评的推荐及个人喜好点了「牛骨高汤」「手切鲜羊肉」「羊上脑」「手切精品鲜黄瓜条」「精品太阳肉」仨「羊肉串儿」「糖卷果」肚子实在没地方了,可精品还没吃,只好「烤火烧」「炸火烧」「肉沫烧饼」「烤腰子」各点一个,三人分着尝口儿。点的肉涮着吃没一点儿膻味儿,还很嫩,麻酱蘸料很好吃!特别是腰子烤的牛极了,外面包的油焦脆,里面的腰子火候正好,美味!各种火烧也很好吃![服务]服务很到位!锅里刚有点儿沫子,服务小妹就帮着撇出去,最后还送了果盘,看我们挺爱吃的,又提示我们果盘还可以续,并送了我们2019年新挂历,谢谢!!"}ToDo如果想要抓取多个商铺的点评数据,可以使用多个账户+多代理+UA池来绕过反爬尝试使用selenium对爬取点评数据进行验证码滑动验证使用手机接码平台注册多个账户进行模拟登陆后获取cookie进行爬取点评数据 ...

January 14, 2019 · 3 min · jiezi

抓包软件 Fiddler 了解一下?

学会如何抓包,是爬虫的必备技能,甚至可以说,不会抓包就等同于不会爬虫。那我们怎样抓包呢?如果直接抓取浏览器上的内容,可以直接使用开发者工具进行抓包,但有个局限,只能抓浏览器的,功能也没有多少。还可以使用别的工具,比如 mitmproxy、charles,当然还有今天所说的 Fiddler 。今天要分享的就是如何使用fiddler进行抓包以及它的功能讲解。1. 下载并安装fiddler下载地址:https://www.telerik.com/downl…这里填写下你的下载目的和国家就可以下载了,安装的话这里就不多说了。2. Fiddler工作原理以及优缺点图片来自https://blog.csdn.net/DreamTL…如上图,Fiddler 作为一个代理,先是捕捉到客户端的 request 请求,然后再自己转发到服务器端,服务器接收到请求时,会返回一个响应 response ,Fiddler 还是会继续捕捉到服务器的响应请求,再来转发给客户端,简单来说, Fiddler 就是作为一个中间人。优缺点:只支持 http、https、ftp、webscoket 数据流等相关协议的捕捉,无法监测或修改其他数据流,如 SMTP、POP3 协议(邮箱相关的协议),无法处理请求和响应超过 2GB 的数据,还有就是只支持 windows 平台,如果想要支持 mac 的话,建议下载 charles ,不过很吃性能,在我机子上运行下那风扇就想起来了。3. Fiddler 功能详解3.1 如何进行抓包1)先勾选允许抓取 https 流量这个就是允许抓取 https 的流量。如果第一次使用,勾选之后会提示你安装一个证书,这个证书就是用来做中间人进行抓包的, Fiddler使用此证书来解密所捕捉到的包,再加密转发到相对应的服务器端或者客户端。如果你还没有安装就点击 Yes 安装即可,如果没有弹出此窗口的也可以点击右上角的 actio 按钮的第一个选项也是可以弹出此个窗口的上面圈出来的是表示抓取哪一部分的流量。from all processes:抓取所有进程的流量from browsers only:只抓取浏览器的流量from non-browsers only:不抓取浏览器的流量from remote clients only:抓取远程的客户端,当需要抓取的是手机端的流量,就需要用到这个2)接下来设置端口号一般默认即可,这里我设置成了8889。下面的那个 Allow remote computers to connect 是允许远程的客户端进行连接,如果抓取手机端的也需要勾选。3)浏览器设置代理服务器Fiddler 的设置完了,这时候还需要在你的浏览器上设置代理服务器才能进行抓取。使用 Chrome 浏览器的可以直接使用 SwitchyOmega 插件进行修改即可,操作简单。点击新增情景模式选择代理服务器,随后填写任意名字,这里我填写的是 Fiddler,点击创建即可。然后填写以上内容,代理服务器为本机,端口号为上面在 Fiddler 设置的端口号,填写完在左下角点应用选项才算设置完毕。设置完之后再在浏览器插件处点击该插件,然后选择该模式即可开始抓包设置完之后第一次打开可能是这样的关闭 Fiddler 再重新打开就可以了3.2 进行手机端抓包手机端的抓包也是很容易的,先是设置好 Fiddler 的允许抓取远程客户端。这时用手机连接wifi,然后长按修改网络(不同安卓手机不一样)点击代理,然后点击手动主机名就写你电脑上的 ip 地址,查看 ip 地址可以在控制台上输入 ipconfig 即可由于我的电脑也是连接 wifi 的,所以 ip 地址对应的是 无线网络的那个,如果你的电脑是使用网线来进行上网的,那 ip 地址就是 以太网的那个。填完之后还不能抓取,如果直接抓取会显示证书有问题。我们也是需要安装证书才可以正常抓取的。那接下来安装证书手机浏览器输入 你的ip地址:端口号 进入网址下载证书,如 192.168.1.2:8888, 端口号还是之前在 Fiddler设置的那个点击上图箭头的网址进行下载即可,下载完毕之后点击安装即可。如果你是 miui 系统的机子,就需要进入wifi 设置的界面进行安装点击 高级设置 –> 安装证书 即可,期间需要密码验证或者设置密码之类的设置即可。还有一个大坑,就是如果你的机子 是安卓 9 而是 miui 系统(其他系统没测试过)的话,安装了证书也是没用的,在进行抓包的时候还是会提示证书有问题。安卓 8 版本的我没有测试过,不过安卓 7 版本以下的估计都可以。弄完了以上的东西就可以抓包了,如果设置完了,网络没了,还是那样子,第一次设置完需要重启下 Fiddler 软件就可以抓包了。3.3 抓包内容的介绍很明显左边的就是捕捉的请求和响应,右边的就是对应请求的详细信息,比如请求头,表单信息,比如上图下面箭头所指的就是表单信息。,如果这些信息看到的内容很少的话,可以直接点击下面的 View in Notepad 按钮就可以在笔记本中显示出来,非常方便。左边每列代表的含义为:左边第一列中每个图片代表的含义为:3.4 再说几个常用的功能查找:抓包时,经常会抓到一堆不重要的包,而需要找的包夹杂在里面非常难找,所以就可以用关键字来查找,入口为:也可以直接点击这个或者直接按快捷键 Ctrl + F 即可这里的功能很强大,可以只查找请求或者响应或者两个都查,还可以用正则表达式来查找,就不一 一说了。映射:也就是重定向,将服务器端的响应内容可以更改为客户端上的文件,功能也是很强大,之前我在爬取网易云评论时也是弄过的,有兴趣的可以看看利用python爬取网易云音乐,并把数据存入mysql。在这里填写对应的规则和文件即可还有一个类似于 postman的功能,就是下面这个就是在这里模拟请求,有什么需要模拟的话可以先在这里模拟一次,成功之后再用编程去敲出来也是不错的,非常强大。由于篇幅问题,还有一些功能就暂时不介绍了,等以后用到的时候会说哈,比如断点调试之类的,到时记得时刻关注哦! ...

January 14, 2019 · 1 min · jiezi

在Chrome控制台注入npm模块

上次研究了如何在java内置javascript解释器nashorn中加载npm模块,这两天手头又有一个需求,要在Chrome浏览器的开发者控制台中加载同样的npm模块,以便在控制台中验证一些想法。因为对前端开发不算熟悉,不知道有没有其它的好方案,这个是我自己摸索的,但确实可用。流程npm下载browserifynpm i browserify -g写一个简单js文件,使用require加载npm模块,并注入到window对象window.acorn = require("./acorn")使用browserify打包这个js,该工具会自动把所有依赖的npm模块和简单js打包成单个js文件browserify entry.js > require_acorn.js把require_acorn.js文件的内容粘贴到开发者控制台并执行,然后就可以使用了代码压缩到这里其实桌面Chrome浏览器就没有问题了,我又用inspector连上安卓版Chrome试了一下,结果控制台崩了……估计是acorn太大了,毕竟有5000多行,于是又试了试压缩:npm下载uglify.jsnpm i uglify-js -g压缩前面生成的单个js包uglifyjs require_acorn.js > require_acorn.min.js这次果然没有问题了,在控制台执行typeof acorn可看到模块已经正确加载。

January 11, 2019 · 1 min · jiezi

关于百度翻译接口的一点吐槽

语言代码既不符合ISO 639-2,也不符合 ISO-639-1 简直了…………

December 29, 2018 · 1 min · jiezi

某网站高度加密混淆的javascript的分析与破解思路

对某网站加密混淆后的javascript代码也算分析了一段时间了,虽然还没搞出来,但多少有些心得,这里记录一下。工具和资料前一篇文章 - 记录了之前尝试的一些方案awesome-java-crawler - 我收集的爬虫相关工具和资料java-curl - 本人写的java HTTP库,可用来替换chrome网络后端,更方便控制底层行为,如缓存、代理、监控、修改请求和应答等cdp4j - java版的Chrome Devtools Protocol实现,用于控制Chrome浏览器。最大的特点就是没有“特点”,你懂的……beautifier.io - js代码在线格式化estree - ECMAScript抽象语法树(AST)业界标准ECMAScript262语言规范 - 帮助理解estreeacornjs - ECMAScript编译器前端,将js源码解析成estree格式的ASTastring - ECMAScript代码生成器,将AST重新还原成js源码nashorn - java8以上自带的javascript解释器,性能接近原生nodejava中调用npm模块 - 我一直用的java和kotlin,为了调用js原生库,需用这个方案类似网站的破解 - 神箭手云的大佬写的很早的一篇分析文 - 看特征是这种加密的早期版本分析过程获取javascript代码加密的核心代码只有一小部分是直接写在网页的<script>里面的,有些代码是eval出来的可以用cdp4j监听Debugger.ScriptParsed事件,并在监听器中调用Debugger.getScriptSource来获取js代码文本这样是可以获取到所有前端javascript源码的,即使源码在网络应答中是加密的,但用eval执行前也必须还原为合法的js源码为了方便分析,可将代码保存为文件。该网站js会用定时器不断重复eval一段代码,因此可以用ScriptParsed.hash作为文件名,避免重复保存文件获取常量映射拿到js之后,格式化一下,发现还是一团乱麻,所有的变量,函数都是"$xx",可读性约等于0在Chrome控制台里试了一下,发现全局变量和函数都保存在window中了一部分无参调用的函数,其实返回的就是常量字符串还有一些$xx.call的,看了一下,其实就是系统方法,比如String.fromCharChode等因此可以编写一段代码,遍历window对象中所有形似_$xx的成员,这样既可将常量字符串映射、系统方法映射等搞出来可读性还原拿到映射关系之后是不是简单用正则表达式替换回去就万事大吉了呢?哪有那么简单!函数的局部变量、局部函数有很大可能性和全局变量重名,如果用正则无脑替换回去绝对会被坑死!!要是代码少倒也罢了,这里可有5000行代码,差之毫厘谬以千里!另外,不同函数的局部变量也存在大量重名,静态分析时干扰严重,因此,应该将局部变量也替换成唯一且更有意义的名字,比如<函数名><变量索引>因此,正确的方法是基于编译原理进行语法级别的替换,看到这里是不是要弃疗了?老子爬点数据还要写编译器?!还好,js上已经有很成熟的业界标准和若干老练的第三方库了,至少不用从龙书搞起……我这里选择了acornjs和astring,前者用于将js源码解析成抽象语法树AST,后者将AST还原成js源码。当然,在AST上我们是可以上下其手的……为了在java代码中运行acornjs和astring,请参见参考中《java中调用npm模块》一文。注意astring还依赖endswith和repeat两个polyfill,均可以npm下载到用acorn.parse()搞到AST之后,用递归的方式扫描每个节点进入每个FunctionDeclaration/FunctionExpression节点前,创建一个新的作用域对象放到栈顶,里面放该域内所有局部变量(含函数的参数)和新名称的映射表;退出时将栈顶弹出遇到Identifier节点,首先在作用域栈中自顶向下依次寻找当前变量名,找到了,则是本方法局部变量或闭包外局部变量,用新名字替换之;否则,则是全局变量,去映射表中查找注意,遇到CallExpression则要特殊处理,前面的AST变换只涉及修改标识符名,而为了将$xx()变换为"xxx",则涉及到结构变换,要把CallExpression节点修改为Literal节点并添加value属性全部处理完成后,就可以用astring.generate()产生还原后的代码了代码分析上面步骤完成后,这代码至少勉强能看了,别放松,后面还有无数的坑……还原前的代码只能是让人一脸懵逼,还原后的代码则足以让人咬牙切齿啊,多大仇啊,满满登登5000行全是正面硬钢的……我现在也就还卡在这一步,这里先记录一部分已经发现的反破解手法吧。不断主动中断干扰调试,并检测是否有动态分析行为var eI_v1 = window[“eval”]("(function() {var a = new Date(); debugger; return new Date() - a > 100;}())");_$n1 = _$n1 || eI_v1;//这个在上篇文章分析了,在这找到调用来源了。注意,在可读性还原之前这货长这样:var _$pW = $u9_$mz();$n1 = _$n1 || $pW;js代码动态混淆上一篇文章已经说过了,每次刷新js代码都会完全变化,包括全局/局部变量名、函数排列顺序等设断点会被干扰,而且代码无法重复执行对于调试意味着什么?检查关键函数是否被注入替换function __RW_checkNative(rh_p0, rh_p1) { // 函数名我手动改的 try { var rh_v2 = Function[“prototype”][“toString”]“apply”; var rh_v3 = new RegExp("{\s*\[native code\]\s*}"); if (typeof rh_p0 !== “function” || !rh_v3“test” || rh_p1 != undefined && rh_p0 !== rh_p1) _GL_undefined$sy = true; } catch ($r0) {}}会用这个函数检测eval, Function, setTimeout, setInterval几个系统函数是不是被注入了搞明白了,就可以用一些手段骗过去,不明白的话……检测当前窗口是否隐藏状态 document[“addEventListener”](“visibilitychange”, _$r0);会监控当前窗口是否在最上方,要是用cdp4j多开浏览器同时爬取……检测Selenium, WebDriver, PhantomJS var rm_v5 = “_Selenium_IDE_Recorder,selenium,callSelenium” , rm_v6 = “__driver_evaluate,__webdriver_evaluate,__selenium_evaluate,__fxdriver_evaluate,__driver_unwrapped,__webdriver_unwrapped,__selenium_unwrapped,__fxdriver_unwrapped,__webdriver_script_func,__webdriver_script_fn” , rm_v7 = [“selenium”, “webdriver”, “driver”]; if ($un(window, “callPhantom,_phantom”)) { … }看到这里想必就知道会发生些什么了……Hook住AJAX var ec_v4 = window[“XMLHttpRequest”]; if (ec_v4) {var ec_v5 = ec_v4[“prototype”];if (ec_v5) { __GL_f_open = ec_v5[“open”]; __GL_f_send = ec_v5[“send”]; ec_v5[“open”] = function () { _$t5(); arguments[1] = $pK(arguments[1]); return __GL_f_open[“apply”](this, arguments); };} else { … } }检查navigator是否是伪造的 var hi_v14 = window[“navigator”]; for (hi_v11 in hi_v14) {try { hi_v13 = hi_v14“hasOwnProperty”;} catch ($r0) { hi_v13 = false;} }如果你的navigator对象是自己注入的水货版本,那就露馅了……检查浏览器特征这块代码很复杂,还没分析完,现在能看出来的包括:navigator.languages - 据说在headless chrome中是没有这个字段的navigator.plugins - 据说无头和有头的chrome返回的插件列表不一样WebGL能力检查有一大段代码是在canvas上用webgl绘图,没搞过webgl,现在还不明白,但肯定也是检查浏览器特征手段之一破解思路其实并没有,还在抓瞎中……一些很不成熟的点子:可以拦截window对象的写入,把全局函数通过toString()搞到源码,再通过acorn/astring大法搞事情之后替换成eval版本但是函数名、变量名全部都混淆了,因此需要用一些特征来检测每个函数,目前想到的点子是将AST上所有节点分类计数连接成字符串,这样绝大多数方法的特征都是唯一的需要将浏览器特征检测代码在桌面浏览器、手机浏览器、桌面无头浏览器分别运行,看看到底有啥区别,然后注入代码修改特征进行欺骗 ...

December 27, 2018 · 1 min · jiezi

使用代理处理反爬抓取微信文章

目标使用代理反爬抓取微信文章,获取文章标题、内容、公众号等信息,并存储到MongoDB数据库中。流程框架如果要抓取微信公众号文章可以使用搜狗的搜索引擎,它会显示最新的文章,但是有两个问题需要大家注意:如果要抓取某一个主题(比如微信风景文章)的所有记录的话,需要先登录(也就是你的请求头headers中要有登陆之后服务器返回的cookies),未登录只可以查看10页,登录之后可以查看100页搜狗微信站点的反爬措施比较严格,如果只是用本地IP(单IP)去抓取的话肯定是不行的,这个时候我们需要用到代理池技术(通过可用随机代理去绕过反爬机制)关于代理池的实现以及使用可以参考这篇文章:使用Redis+Flask维护动态代理池下图展示了具体的流程框架:(1)抓取索引页内容def parse_index(html): doc = pq(html) items = doc(’.news-box .news-list li .txt-box h3 a’).items() for item in items: yield item.attr(‘href’)def parse_index(html): doc = pq(html) items = doc(’.news-box .news-list li .txt-box h3 a’).items() for item in items: yield item.attr(‘href’)在流程框架部分我们提到,在此将要使用搜狗搜索微信站点文章,首先让我们进入搜狗搜索界面https://weixin.sogou.com/,比如输入关键字风景,就会出现微信文章的列表。从网页的url可以看出这是一个get请求,只保留主要参数,可以把url简化为其中,“query”代表搜索的关键词,“type”代表搜索结果的类型,“type=1”表示搜索结果是微信公众号,“type=2”表示搜索结果是微信文章,“page”也就是当前页数。分析完网页的url组成之后,我们先解决第一个问题:保存cookie,模拟登录。打开浏览器控制台,选择NetWork->Headers选项卡,可以看到请求的headers信息。解决完以上问题之后,让我们尝试写一下代码获取第1-100页的网页源码:from urllib.parse import urlencodeimport requestsbase_url = ‘https://weixin.sogou.com/weixin?'# 构造请求头headers = { ‘Cookie’: ‘CXID=DF1F2AE56903B8B6289106D60E0C1339; SUID=F5959E3D8483920A000000005BCEB8CD; sw_uuid=3544458569; ssuid=8026096631; pex=C864C03270DED3DD8A06887A372DA219231FFAC25A9D64AE09E82AED12E416AC; SUV=00140F4F78C27EE25BF168CF5C981926; ad=p7R@vkllll2bio@ZlllllVsE@EclllllNBENLlllll9lllllpA7ll5@@@@@@@@@@; IPLOC=CN4110; ABTEST=2|1543456547|v1; weixinIndexVisited=1; sct=1; JSESSIONID=aaaXtNmDWRk5X5sEsy6Cw; PHPSESSID=lfgarg05due13kkgknnlbh3bq7; SUIR=EF72CF750D0876CFF631992E0D94BE34;’, ‘Host’: ‘weixin.sogou.com’, ‘Upgrade-Insecure-Requests’: ‘1’, ‘User-Agent’: ‘Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36’}def get_html(url, count=1): response = requests.get(url, allow_redirects=False, headers=headers) # 判断网页返回的状态码是否正常 # 如果状态码是200说明可以正常访问 if response.status_code == 200: return response.text # 如果状态码是302,则说明IP已经被封 if response.status_code == 302: return Nonedef get_index(keyword, page): data = { ‘query’: keyword, ’type’: 2, ‘page’: page } queries = urlencode(data) url = base_url + queries html = get_html(url) return htmldef main(): for page in range(1, 101): html = get_index(‘风景’, page) print(html) if name == ‘main’: main()运行以上代码,会发现刚开始运行正常,正确返回网页源码,之后便一直返回None,此时让我们打开浏览器观察一下:可以看出,代码运行后不停返回None的原因是网页被重定向,需要输入验证码才能正常访问,这便是我们开篇说过的第二个问题,我们的访问被搜狗搜索的反爬虫措施拦截,如果想要继续正常访问,便需要利用代理池获取随机代理来绕过反爬机制。(2)代理设置在使用Redis+Flask维护动态代理池一文中,我们讲解了代理池的基本原理和简单实现,代码已托管到github上,现在让我们利用代理池来获取随机代理。首先让我们定义get_proxy()方法,返回代理池获取的随机可用ip:# flask监听的是5000端口PROXY_POOL_URL = ‘http://127.0.0.1:5000/get’def get_proxy(): try: response = requests.get(PROXY_POOL_URL) if response.status_code == 200: return response.text return None except ConnectionError: return None接下来修改get_html(url, count=1)方法,以随机ip的方式访问网页:MAX_COUNT = 5proxy = Nonedef get_html(url, count=1): # 打印抓取的url print(‘Crawling’, url) # 打印尝试抓取的次数 print(‘Trying Count’, count) global proxy # 如果抓取的次数大于最大次数,则返回None if count >= MAX_COUNT: print(‘Tried Too Many Counts’) return None try: if proxy: proxies = { ‘http’: ‘http://’ + proxy } # allow_redirects=False:禁止浏览器自动处理302跳转 response = requests.get(url, allow_redirects=False, headers=headers, proxies=proxies) else: response = requests.get(url, allow_redirects=False, headers=headers) if response.status_code == 200: return response.text # 状态码是302,说明IP已经被封,调用get_proxy()获取新的ip if response.status_code == 302: # Need Proxy print(‘302’) proxy = get_proxy() if proxy: print(‘Using Proxy’, proxy) return get_html(url) else: print(‘Get Proxy Failed’) return None except ConnectionError as e: # 如果连接超时,重新调用get_html(url, count)方法 print(‘Error Occurred’, e.args) proxy = get_proxy() count += 1 return get_html(url, count)再次运行代码,会发现不停重复打印None的情况基本消失。大家注意,这里是基本消失,原因是我们的代理池使用的是免费代理网站获取的代理,同一时刻可能会有许多人访问,这样就很容易造成ip地址被封的情况。如果你想要获取更好的效果,不妨使用一下收费代理。至此,我们解决了开篇提到的两个问题,接下来,就可以抓取网页,分析内容。(3)分析详情页内容首先我们需要获取到第1-100页中每一篇文章的url:def parse_index(html): doc = pq(html) items = doc(’.news-box .news-list li .txt-box h3 a’).items() for item in items: yield item.attr(‘href’) def main(): for page in range(1, 101): html = get_index(KEYWORD, page) if html: article_urls = parse_index(html) print(article_urls)获取到每一篇文章的url之后,就需要解析每一篇文章的内容。解析方法与上面相同,在此不再赘述。具体代码如下:def parse_detail(html): try: doc = pq(html) title = doc(’.rich_media_title’).text() content = doc(’.rich_media_content ‘).text() date = doc(’#publish_time’).text() nickname = doc(’.rich_media_meta_list .rich_media_meta_nickname’).text() wechat = doc(’#activity-name’).text() return { ’title’: title, ‘content’: content, ‘date’: date, ’nickname’: nickname, ‘wechat’: wechat } except XMLSyntaxError: return None需要注意的一点就是需要捕获XMLSyntaxError异常。(4)将数据保存到数据库最后让我们新建一个config.py文件,文件中包含了MongoDB的URL,数据库名称,表名称等常量:MONGO_URL = ’localhost’MONGO_DB = ‘weixin’在spider.py中配置存储到MongoDB相关方法:from config import *import pymongoclient = pymongo.MongoClient(MONGO_URL)db = client[MONGO_DB]def save_to_mongo(data): if db[‘articles’].update({’title’: data[’title’]}, {’$set’: data}, True): print(‘Saved to Mongo’, data[’title’]) else: print(‘Saved to Mongo Failed’, data[’title’])运行代码,接下来在MongoDB中进行查看:项目完整代码已托管到github:https://github.com/panjings/p… ...

December 19, 2018 · 2 min · jiezi

写爬虫还在用 python?快来试试 go 语言的爬虫框架吧

今天为大家介绍的是一款 go 语言爬虫框架 – colly。开始首先,你可以使用一下命令安装 colly。go get -u github.com/gocolly/colly/…其次,构建 Collector,添加事件,然后访问:package mainimport ( “fmt” “github.com/gocolly/colly”)func main() { // 初始化 colly c := colly.NewCollector( // 只采集规定的域名下的内容 colly.AllowedDomains(“hackerspaces.org”, “wiki.hackerspaces.org”), ) // 任何具有 href 属性的标签都会触发回调函数 // 第一个参数其实就是 jquery 风格的选择器 c.OnHTML(“a[href]”, func(e *colly.HTMLElement) { link := e.Attr(“href”) fmt.Printf(“Link found: %q -> %s\n”, e.Text, link) // 访问该网站 c.Visit(e.Request.AbsoluteURL(link)) }) // 在请求发起之前输出 url c.OnRequest(func(r colly.Request) { fmt.Println(“Visiting”, r.URL.String()) }) // 从以下地址开始抓起 c.Visit(“https://hackerspaces.org/")}运行以上代码,会从最开始的地址抓起,一直把规定的两个域名下的页面递归采集完。看,是不是很简单很方便!登录鉴权某些网站的某些页面可能需要登录状态才能访问。Colly 提供 Post 方法用于登录请求(colly 本身会维护 cookie)。// authenticateerr := c.Post(“http://example.com/login", map[string]string{“username”: “admin”, “password”: “admin”})if err != nil { log.Fatal(err)}很多网站可能会有验证码、csrf_token 之类的仿网络攻击策略。对于 csrf_token,一般都会在页面的某个位置,比如表单,或者 mate 标签里,这些都是很容易获取到的。对于验证码,可以尝试在控制台输入结果或者采用图片识别的方式。速率控制很多内容网站会有防采集策略,所以过快的请求速率很可能导致被封 ip。这里可以使用 LimitRule 限制采集速度。// 对于任何域名,同时只有两个并发请求在请求该域名c.Limit(&colly.LimitRule{DomainGlob: “”, Parallelism: 2})上面是一个简单的例子。除了可以限制域名并发量外,还可以限制间隔时间等。我们看一下 LimitRule 的结构:type LimitRule struct { // 匹配域名的正则表达式 DomainRegexp string // glob 匹配模式 DomainGlob string // 在发起一个新请求时的等待时间 Delay time.Duration // 在发起一个新请求时的随机等待时间 RandomDelay time.Duration // 匹配到的域名的并发请求数 Parallelism int waitChan chan bool compiledRegexp *regexp.Regexp compiledGlob glob.Glob}队列与redis存储支持某些情况下,我们的爬虫可能会主动或被动地挂掉,所以一个合理的进度保存有助于我们排除已经爬过的内容。这时候我们就需要用到队列以及存储支持。Colly 本身有文件存储模式,默认是 未开启状态。推荐使用 redis 进行存储。urls := []string{ “http://httpbin.org/", “http://httpbin.org/ip", “http://httpbin.org/cookies/set?a=b&c=d", “http://httpbin.org/cookies",}c := colly.NewCollector()// 创建 redis storagestorage := &redisstorage.Storage{ Address: “127.0.0.1:6379”, Password: “”, DB: 0, Prefix: “httpbin_test”,}// 把 storage 设置到 collector 上err := c.SetStorage(storage)if err != nil { panic(err)}// 删除之前的数据(如果需要)if err := storage.Clear(); err != nil { log.Fatal(err)}// 结束后关闭 redis 连接defer storage.Client.Close()// 使用 redis 作为存储后端,创建请求队列// 消费者数量设定为 2q, _ := queue.New(2, storage)c.OnResponse(func(r *colly.Response) { log.Println(“Cookies:”, c.Cookies(r.Request.URL.String()))})// 把 url 加入到队列for _, u := range urls { q.AddURL(u)}// 开始采集q.Run(c)使用队列时,在解析到页面的链接后,可以继续把链接的 url 添加到队列中。内容解析内容抓取到了,如何解析并获取我们想要的内容呢?以 html 为例(colly 也有 xml 等内容解析):// refentry 内容c.OnHTML(".refentry”, func(element *colly.HTMLElement) { // …})OnHtml 第一个参数是 jquery风格的选择器,第二个参数是 callback,callback 会传入 HTMLElement 对象。HTMLElement 结构体:type HTMLElement struct { // 标签的名称 Name string Text string attributes []html.Attribute // 当前的 request Request *Request // 当前的 response Response *Response // 当前节点的 DOM 元素 DOM *goquery.Selection // 在该 callback 回调中,此 element 的索引 Index int}其中,可以通过 DOM 字段操作(增删节点)、遍历、获取节点内容。DOM 字段是 Selection 类型,该类型提供了大量的方法。如果你用过 jQuery,你一定会觉得熟悉。举个栗子,我们想要删除 h1.refname 标签,并返回父元素的 html 内容:c.OnHTML(".refentry”, func(element *colly.HTMLElement) { titleDom := element.DOM.Find(“h1.refname”) title := titleDom.Text() titleDom.Remove() content, _ := element.DOM.Html() // …})其他除此之外,Colly 还具有其他强大的功能,比如最大递归深度、url 过滤、url revisit(默认一个 url 只访问一次)以及编码检测等。这些都可以在官网文档或者 colly 代码里找到影子。另附上 colly 文档地址:http://go-colly.org/docs/intr… ...

December 7, 2018 · 2 min · jiezi

Python 从入门到爬虫极简教程

Python 爬虫与数据分析你学的太多,练习太少。 – 古典抓取数据但不用 Python不编码是第一选择八爪鱼采集器 Octoparse特点: 内嵌浏览器, 可视化定位, 可提取 JavaScript 生成内容, 提取数据用 xpath, 常用网站模板, 支持云采集, 支持多种数据格式输出和数据库导出http://www.bazhuayu.com/ 5分钟演示 https://v.youku.com/v_show/id…支持部分验证码自动识别 http://www.bazhuayu.com/faq/c…免费版同时2个线程, 最多10个任务火车采集器特点: 对接数据库, 可直接导入 cmshttp://www.locoy.com/很多 cms 自带文章采集工具如 jeecms, phpCMS, dedeCMS, 帝国 cms(略)为什么要学 Python数据分析需要多个阶段, 抓取数据仅是一个环节, 数据需要不断采集, 更新, 清洗, 分析, 可视会展示等多个阶段, 这些过程中 Python 都能应对自如. 属于性阶适中的工具.vs C对比 C 语言, 效率弱一些, 但仅是运行效率, 开发效率高很多, 多数项目恰是开发占比高, 一直开发, 偶尔运行成为常态vs Java无需编译, 省去很多麻烦, 更适合一次性应用, 或小团队使用, 更灵活.Life Is Short, Use PythonAI与机器学习Python 语言基础版本的问题区别Python 2.x 和 3.x 有很大区别2to3使用 2to3 可以自动升级大部分代码3.x 新特性https://www.asmeurer.com/pyth…版本隔离 virtualenv$ pip3 install virtualenv$ virtualenv –no-site-packages venv$ source venv/bin/activate(venv)$ (venv)$ deactivate$常用数据结构{} 大字典 Dictionary 键值对, 键唯一, 按键可以很快随机查找[] 方列表 List 元素存储紧凑, 顺序固定, 可排序(1,) 圆元组 tupleset() 设集合 set 集合中,元素唯一,无相同元素输入输出, 文本处理, 数组处理input 终端输入读文件open(), read() seek()写文件写文件和读文件是一样的,唯一区别是调用open()函数时,传入标识符’w’或者’wb’表示写文本文件或写二进制文件:>>> f = open(’/Users/michael/test.txt’, ‘w’)>>> f.write(‘Hello, world!’)>>> f.close()数组面向对象基本概念与使用如何轻松愉快地学 Python游戏学编程,熟悉语法, 流程结构, 函数等 https://codecombat.com/ ide: pycharm, vs code, 断点调试Python教程练习题猜随机数成三角形概率求质数的几种境界质数概率png 格式简析图形格式介绍png, gif, jpg, svg, webp特色与难点装饰器decorator @生成器generatoryeildlambda 表达式一些常用函数zip()map()filter()网络协议与文件格式URL协议头://域名:端口/路径/文件?参数1=参数值1&参数2=参数值2#页面锚点HTTP 协议https://www.tutorialspoint.co…无连接: 请求之间不需要保持连接 媒介无关: MIME 类型确定数据内容无状态: 用 cookie 或参数跟踪状态请求头通过观察 浏览器 -> 开发者工具 学习重点掌握Cookie Referer User-Agent Content-Type请求方法GET最常见, 一般通过 url 传递参数, 幂等性POST提交操作, 大量数据时, 上传文件时用响应状态码200:请求成功 处理方式:获得响应的内容,进行处理301:请求到的资源都会分配一个永久的URL,这样就可以在将来通过该URL来访问此资源 查看头里的 Location302:请求到的资源在一个不同的URL处临时保存 查看头里的 Location400:非法请求 401:未授权 403:禁止 404:没有找到500:服务器内部错误502:错误网关 作为网关或者代理工作的服务器尝试执行请求时,从上游服务器接收到无效的响应。测试工具curl结合浏览器的使用, -o 参数,wget断点续传之 -c 参数, 批量下载时的通配符使用chromium, telnet, netcatHTML 格式学习工具w3cschool.comjson格式 工具JavaScript & CSS适当了解python常用抓取工具/类库介绍urllibimport urllib2 response = urllib2.urlopen(“http://www.baidu.com”)print response.read()2to3 urllib.pyimport urllib.request, urllib.error, urllib.parse response = urllib.request.urlopen(“http://example.com”)print(response.read())练习指导:Python3 启动, 退出 Ctrl+D2to3 –help 找出 -w 回写参数两种执行方式, 命令行, 交互式参考: https://cuiqingcai.com/947.htmlRequests 库Scrapy$ pip install Scrapy lxmlPySpider非常方便并且功能强大的爬虫框架,支持多线程爬取、JS动态解析,提供了可操作界面、出错重试、定时爬取等等的功能,使用非常人性化。官网安装$ pip install pyspider使用$ pyspider all然后浏览器访问 http://localhost:5000Selenium & PhantomJS$pip install selenium用浏览器进行加载页面 from selenium import webdriver browser = webdriver.Chrome() browser.get(‘http://www.baidu.com/')驱动浏览器进行搜索import unittestfrom selenium import webdriverfrom selenium.webdriver.common.keys import Keys class PythonOrgSearch(unittest.TestCase): def setUp(self): self.driver = webdriver.Chrome() def test_search_in_python_org(self): driver = self.driver driver.get(“http://www.python.org”) self.assertIn(“Python”, driver.title) elem = driver.find_element_by_name(“q”) elem.send_keys(“pycon”) elem.send_keys(Keys.RETURN) assert “No results found.” not in driver.page_source def tearDown(self): self.driver.close() if name == “main”: unittest.main()用 PhantomJS 保存页面为图片PhantomJS 相当于无界面浏览器, 可执行脚本和 CSS 内存渲染phantomjs helloworld.jsvar page = require(‘webpage’).create();page.open(‘http://cuiqingcai.com’, function (status) { console.log(“Status: " + status); if (status === “success”) { page.render(’example.png’); } phantom.exit();}); 数据提取工具html, xml, xpath, selector, json正则表达式掌握起来, 有一定难度, 多数编辑器支持, 使用场景广, 但不适合结构化数据(xml, json, html)Python Re模块提供#返回pattern对象re.compile(string[,flag]) #以下为匹配所用函数re.match(pattern, string[, flags])re.search(pattern, string[, flags])re.split(pattern, string[, maxsplit])re.findall(pattern, string[, flags])re.finditer(pattern, string[, flags])re.sub(pattern, repl, string[, count])re.subn(pattern, repl, string[, count])参见: https://cuiqingcai.com/912.html其于 Dom 模型的 jQuery selector在 Selenium 中或浏览器中直接使用基于查询语言的 XPath 标准XPath语言是基于一个树形结构表示的XML 文档,提供的导航能力,通过多种属性选择节点的一个标准。XPath 是提取 XML 的工具, 所以需要对 HTML正行校正校正工具:BeautifulSoupElementTidy使用 lxml 完成解析 HTML>>> from lxml import etree>>> doc = ‘<foo><bar></bar></foo>’>>> tree = etree.HTML(doc)>>> r = tree.xpath(’/foo/bar’)>>> len(r)1>>> r[0].tag’bar’>>> r = tree.xpath(‘bar’)>>> r[0].tag’bar’最稳定的结果是使用 lxml.html 的 soupparser。你需要安装 python-lxml 和 python-beautifulsoup,然后你可以执行以下操作:from lxml.html.soupparser import fromstringtree = fromstring(’<mal form=“ed”><html/>here!’)matches = tree.xpath(”./mal[@form=ed]")XPath 文档维基 https://en.wikipedia.org/wiki...W3C https://www.w3.org/TR/xpath-30/入门教程https://www.w3schools.com/xml…XPath 在线测试工具https://codebeautify.org/Xpat…特点: 可以直接加载 url<root xmlns:foo=“http://www.foo.org/" xmlns:bar=“http://www.bar.org”> <employees> <employee id=“1”>Johnny Dapp</employee> <employee id=“2”>Al Pacino</employee> <employee id=“3”>Robert De Niro</employee> <employee id=“4”>Kevin Spacey</employee> <employee id=“5”>Denzel Washington</employee> </employees> <foo:companies> <foo:company id=“6”>Tata Consultancy Services</foo:company> <foo:company id=“7”>Wipro</foo:company> <foo:company id=“8”>Infosys</foo:company> <foo:company id=“9”>Microsoft</foo:company> <foo:company id=“10”>IBM</foo:company> <foo:company id=“11”>Apple</foo:company> <foo:company id=“12”>Oracle</foo:company> </foo:companies></root>示例:1.选择文档节点 /2.选择“root”元素 /root3.选择所有’employee’元素,它们是’employees’元素的直接子元素。/root/employees/employee4.选择所有“公司”元素,无论它们在文档中的位置如何。 //foo:company5.选择“公司”元素的“id”属性,无论它们在文档中的位置如何。 //foo:company/@id6.选择第一个“employee”元素的文本值。 //employee[1]/text()7.选择最后一个’employee’元素。//employee[last()]8.使用其位置选择第一个和第二个“employee”元素。 //employee[position() < 3]9.选择具有“id”属性的所有“employee”元素。 //employee[@id]10.选择’id’属性值为'3’的’employee’元素。 //employee[@id=‘3’]11.选择“id”属性值小于或等于“3”的所有“employee”节点。 //employee[@id<=3]12.选择“companies”节点的所有子项。/root/foo:companies/13.选择文档中的所有元素。 // 14.选择所有“员工”元素和“公司”元素。 //employee|//foo:company15.选择文档中第一个元素的名称。 name(//[1])16.选择第一个“employee”元素的“id”属性的数值。 number(//employee[1]/@id)17.选择第一个“employee”元素的“id”属性的字符串表示形式值。 string(//employee[1]/@id)18.选择第一个“employee”元素的文本值的长度。 string-length(//employee[1]/text())19.选择第一个“company”元素的本地名称,即没有命名空间。 string-length(//employee[1]/text())20.选择“公司”元素的数量。count(//foo:company)21.选择’company’元素的’id’属性的总和。 sum(//foo:company/@id)http://www.xpathtester.com/xpath使用示例: 用xpath怎么提取重复元素中的一个元素<div class=“container”> <div class=“col-12 col-sm-3”> <p class=“title”>序号</p> <p>001</p> </div> <div class=“col-12 col-sm-3”> <p class=“title”>编号</p> <p>999</p> </div> <div class=“col-12 col-sm-3”> <p class=“title”>列号</p> <p>321</p> </div></div>//p[text()=“编号”]/following-sibling::p[1]例如:Python+Selenium获取文本:driver.driver.find_element_by_xpath(//p[text()=“编号”]/following-sibling::p[1]).text注: Selenium 支持 XPath 和类 jQuery Selector 等多种选择方式.Firefox 和 XPath2017之前的 firefox 版本 + Firebug2017后 Firefox Developer Edition + Chropath addonhttps://addons.mozilla.org/en…Chromium 和 XPath在Chrome/ Firefox浏览器中打开网站按Ctrl + Shift + I(将打开开发人员工具)Alt+CMD+I选择仪器窗口顶部的“元素”选择仪器窗口底部的放大镜在浏览器中选择所需的元素右键单击DOM树中的选定行,然后选择“复制XPath”Chrome Extension XPath Helper (需要科学上网)数据保存csv 及 excel 格式注意引号转义, 可用现成库MySQL 数据库安装MySQL驱动由于MySQL服务器以独立的进程运行,并通过网络对外服务,所以,需要支持Python的MySQL驱动来连接到MySQL服务器。MySQL官方提供了mysql-connector-python驱动,但是安装的时候需要给pip命令加上参数–allow-external:$ pip install mysql-connector-python –allow-external mysql-connector-python如果上面的命令安装失败,可以试试另一个驱动:$ pip install mysql-connector我们演示如何连接到MySQL服务器的test数据库:# 导入MySQL驱动:>>> import mysql.connector# 注意把password设为你的root口令:>>> conn = mysql.connector.connect(user=‘root’, password=‘password’, database=‘test’)>>> cursor = conn.cursor()# 创建user表:>>> cursor.execute(‘create table user (id varchar(20) primary key, name varchar(20))’)# 插入一行记录,注意MySQL的占位符是%s:>>> cursor.execute(‘insert into user (id, name) values (%s, %s)’, [‘1’, ‘Michael’])>>> cursor.rowcount1# 提交事务:>>> conn.commit()>>> cursor.close()# 运行查询:>>> cursor = conn.cursor()>>> cursor.execute(‘select * from user where id = %s’, (‘1’,))>>> values = cursor.fetchall()>>> values[(‘1’, ‘Michael’)]# 关闭Cursor和Connection:>>> cursor.close()True>>> conn.close()爬虫常见问题常见反爬技术User-Agent新华网 Referer频率36kr.comtaobao.com用户点击才展示内容csdn.net 博客登录后可用内容taobao.com各种人机验证 Captcha封IP, 封ID编码问题 GB2312, GB18030, GKB, UTF-8, ISO8859-1GB18030 > GBK > GB2312 但相互兼容UTF-8与以上编码不兼容用代理隐藏 ipimport requestsfrom lxml import etreeheaders = { ‘User-Agent’: ‘Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36’ }url = ‘https://ip.cn/'## 下面的网站是用来获取代理ip的APIip_url = ‘http://proxy.w2n1ck.com:9090/random'ip = {‘http’ : ‘http://’+requests.get(ip_url).text}print(ip)response = requests.get(url, headers=headers, proxies=ip, timeout=10).texthtml = etree.HTML(response)## 提取页面显示的ipres = html.xpath(’//[@id=“result”]/div/p[1]/code/text()’)print(res)模拟登录图形验证码处量百度OCR https://aip.baidubce.com/rest…Tesseract + openCVML-OCR效果最好人工OCR手工录入数据可视化matplotechartsTableau高级话题手机 APP 接口数据抓取Python3.x+Fiddler抓取APP数据思路是电脑共享 wifi, 手机连这个 wifi, 电脑wifi 的 IP做为代理, 手机上设置代理.手机信任电脑的代理证书. 中间人攻击完成了. 截获到网络请求再通过参数变换完成抓取https://segmentfault.com/a/11…分布式爬虫数据库或缓存为协调工具中文分词结巴分词自然言语分析hanlp tlp-cloud人脸识别阿里的接口图形识别有问题到哪里去问?Courserastackoverflow.com思否(c) 2018 Yujiaao微信: yujiabuao ...

November 22, 2018 · 3 min · jiezi

puppeteer爬虫

@(爬虫)[puppeteer|]爬虫又称网络机器人。每天或许你都会使用搜索引擎,爬虫便是搜索引擎重要的组成部分,爬取内容做索引。现如今大数据,数据分析很火,那数据哪里来呢,可以通过网络爬虫爬取啊。那我萌就来探讨一下网络爬虫吧。[TOC]爬虫的工作原理如图所示,这是爬虫的流程图,可以看到通过一个种子URL开启爬虫的爬取之旅,通过下载网页,解析网页中内容存储,同时解析中网页中的URL 去除重复后加入到等待爬取的队列。然后从队列中取到下一个等待爬取的URL重复以上步骤,是不是很简单呢?广度(BFS)还是深度(DFS)优先策略上面也提到在爬取完一个网页后从等待爬取的队列中选取一个URL去爬去,那如何选择呢?是选择当前爬取网页中的URL 还是继续选取当前URL中同级URL呢?这里的同级URL是指来自同一个网页的URL,这就是爬取策略之分。广度优先策略(BFS)广度优先策略便是将当前某个网页中URL先爬取完全,再去爬取从当前网页中的URL爬取的URL,这就是BFS,如果上图的关系图表示网页的关系,那么BFS的爬取策略将会是:(A->(B,D,F,G)->(C,F));深度优先策略(DFS)深度优先策略爬取某个网页,然后继续去爬取从网页中解析出的URL,直到爬取完。(A->B->C->D->E->F->G)下载网页下载网页看起来很简单,就像在浏览器中输入链接一样,下载完后浏览器便能显示出来。当然结果是并不是这样的简单。模拟登录对于一些网页来说需要登录才能看到网页中内容,那爬虫怎么登录呢?其实登录的过程就是获取访问的凭证(cookie,token…)let cookie = ‘’;let j = request.jar()async function login() { if (cookie) { return await Promise.resolve(cookie); } return await new Promise((resolve, reject) => { request.post({ url: ‘url’, form: { m: ‘username’, p: ‘password’, }, jar: j }, function(err, res, body) { if (err) { reject(err); return; } cookie = j.getCookieString(‘url’); resolve(cookie); }) })}这里是个简单的栗子,登录获取cookie, 然后每次请求都带上cookie.获取网页内容有的网页内容是服务端渲染的,没有CGI能够获得数据,只能从html中解析内容,但是有的网站的内容并不是简单的便能获取内容,像linkedin这样的网站并不是简单的能够获得网页内容,网页需要通过浏览器执行后才能获得最终的html结构,那怎么解决呢?前面我萌提到浏览器执行,那么我萌有没有可编程的浏览器呢?puppeteer,谷歌chrome团队开源的无头浏览器项目,利用无头浏览器便能模拟用户访问,便能获取最重网页的内容,抓取内容。利用puppeteer 模拟登录async function login(username, password) { const browser = await puppeteer.launch(); page = await browser.newPage(); await page.setViewport({ width: 1400, height: 1000 }) await page.goto(‘https://maimai.cn/login'); console.log(page.url()) await page.focus(‘input[type=text]’); await page.type(username, { delay: 100 }); await page.focus(‘input[type=password]’); await page.type(password, { delay: 100 }); await page.$eval(“input[type=submit]”, el => el.click()); await page.waitForNavigation(); return page;}执行login()后便能像在浏览器中登录后,便能像浏览器中登录后便能获取html中的内容,当让w哦萌也可以直接请求CGIasync function crawlData(index, data) { let dataUrl = https://maimai.cn/company/contacts?count=20&amp;page=${index}&amp;query=&amp;dist=0&amp;cid=${cinfo.cid}&amp;company=${cinfo.encodename}&amp;forcomp=1&amp;searchTokens=&amp;highlight=false&amp;school=&amp;me=&amp;webcname=&amp;webcid=&amp;jsononly=1; await page.goto(dataUrl); let res = await page.evaluate((e) => { return document.body.querySelector(‘pre’).innerHTML; }); console.log(res) res = JSON.parse(res); if (res && res.result == ‘ok’ && res.data.contacts && res.data.contacts.length) { data = data.concat(res.data.contacts.map((item) => { let contact = item.contact; console.log(contact.name) return { name: contact.name, occupation: contact.line4.split(’,’)[0], company: contact.company, title: contact.position } })); return await crawlData(++index, data); } return data; }像有的网站,拉钩,每次爬取的cookie都一样,也能利用无头浏览器取爬取,这样每次就不用每次爬取的时候担心cookie.写在最后当然爬虫不仅仅这些,更多的是对网站进行分析,找到合适的爬虫策略。对后关于puppeteer,不仅仅可以用来做爬虫,因为可以编程,无头浏览器,可以用来自动化测试等等。 ...

November 16, 2018 · 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

Python 爬虫利器 Selenium

前面几节,我们学习了用 requests 构造页面请求来爬取静态网页中的信息以及通过 requests 构造 Ajax 请求直接获取返回的 JSON 信息。还记得前几节,我们在构造请求时会给请求加上浏览器 headers,目的就是为了让我们的请求模拟浏览器的行为,防止被网站的反爬虫策略限制。今天要介绍的 Selenium 是一款强大的工具,它可以控制我们的浏览器,这样一来程序的行为就和人类完全一样了。通过使用 Selenium 可以解决几个问题:页面内容是由 JavaScript 动态生成,通过 requests 请求页面无法获取内容。爬虫程序被反爬虫策略限制让程序的行为和人一样安装pip install selenium安装浏览器驱动驱动下载地址下载后把驱动文件加入环境变量。或者直接把驱动文件和 Python脚本放到同一文件夹下面测试 安装完成后,可以编写以下脚本来测试是否安装成功。from selenium import webdriverdriver = webdriver.Chrome() # 创建一个 Chrome WebDriver 实例driver.get(‘https://www.baidu.com/') # 打开网址运行后会发现程序自动打开了 Chrome 浏览器,并且定向到了百度首页。与页面交互 WebDriver定义了很多方法,我们可以很方便的操作页面上的元素 比如获取元素,可以通过 driver.find_element_by_id(“id”)或者driver.find_element_by_name(“name”)以及 xpath路径的方式来获取元素。可以通过send_keys 向输入框中写入文本。from selenium import webdriverdriver = webdriver.Chrome()driver.get(‘https://www.baidu.com/')search_input = driver.find_element_by_id(“kw”) # 获取到百度搜索框search_input.send_keys(“刘亦菲”) # 自动输入 刘亦菲submit = driver.find_element_by_id(“su”) # 获取到百度一下按钮submit.click() # 点击搜索运行以上脚本,程序会自动打开 Chrome 浏览器,并自动搜索 刘亦菲其他操作 Selenium 可以进行各种各样的操作,使程序完全符合人类的操作习惯。下面看一下还有哪些功能。具体可以看官方文档,这里贴一下地址https://selenium-python-zh.readthedocs.io/en/latest/index.html

September 28, 2018 · 1 min · jiezi

Python爬虫——Python 岗位分析报告

前两篇我们分别爬取了糗事百科和妹子图网站,学习了 Requests, Beautiful Soup 的基本使用。不过前两篇都是从静态 HTML 页面中来筛选出我们需要的信息。这一篇我们来学习下如何来获取 Ajax 请求返回的结果。欢迎关注公号【智能制造专栏】学习更多原创智能制造及编程知识。Python 爬虫入门(二)——爬取妹子图 Python 爬虫入门(一)——爬取糗百本篇以拉勾网为例来说明一下如何获取 Ajax 请求内容本文目标获取 Ajax 请求,解析 JSON 中所需字段数据保存到 Excel 中数据保存到 MySQL, 方便分析简单分析五个城市 Python 岗位平均薪资水平Python 岗位要求学历分布Python 行业领域分布Python 公司规模分布查看页面结构我们输入查询条件以 Python 为例,其他条件默认不选,点击查询,就能看到所有 Python 的岗位了,然后我们打开控制台,点击网络标签可以看到如下请求:从响应结果来看,这个请求正是我们需要的内容。后面我们直接请求这个地址就好了。从图中可以看出 result 下面就是各个岗位信息。到这里我们知道了从哪里请求数据,从哪里获取结果。但是 result 列表中只有第一页 15 条数据,其他页面数据怎么获取呢?分析请求参数我们点击参数选项卡,如下:发现提交了三个表单数据,很明显看出来 kd 就是我们搜索的关键词,pn 就是当前页码。first 默认就行了,不用管它。剩下的事情就是构造请求,来下载 30 个页面的数据了。构造请求,并解析数据构造请求很简单,我们还是用 requests 库来搞定。首先我们构造出表单数据 data = {‘first’: ’true’, ‘pn’: page, ‘kd’: lang_name} 之后用 requests 来请求url地址,解析得到的 Json 数据就算大功告成了。由于拉勾对爬虫限制比较严格,我们需要把浏览器中 headers 字段全部加上,而且把爬虫间隔调大一点,我后面设置的为 10-20s,然后就能正常获取数据了。import requestsdef get_json(url, page, lang_name): headers = { ‘Host’: ‘www.lagou.com’, ‘Connection’: ‘keep-alive’, ‘Content-Length’: ‘23’, ‘Origin’: ‘https://www.lagou.com’, ‘X-Anit-Forge-Code’: ‘0’, ‘User-Agent’: ‘Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:61.0) Gecko/20100101 Firefox/61.0’, ‘Content-Type’: ‘application/x-www-form-urlencoded; charset=UTF-8’, ‘Accept’: ‘application/json, text/javascript, /; q=0.01’, ‘X-Requested-With’: ‘XMLHttpRequest’, ‘X-Anit-Forge-Token’: ‘None’, ‘Referer’: ‘https://www.lagou.com/jobs/list_python?city=%E5%85%A8%E5%9B%BD&cl=false&fromSearch=true&labelWords=&suginput=', ‘Accept-Encoding’: ‘gzip, deflate, br’, ‘Accept-Language’: ’en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7’ } data = {‘first’: ‘false’, ‘pn’: page, ‘kd’: lang_name} json = requests.post(url, data, headers=headers).json() list_con = json[‘content’][‘positionResult’][‘result’] info_list = [] for i in list_con: info = [] info.append(i.get(‘companyShortName’, ‘无’)) info.append(i.get(‘companyFullName’, ‘无’)) info.append(i.get(‘industryField’, ‘无’)) info.append(i.get(‘companySize’, ‘无’)) info.append(i.get(‘salary’, ‘无’)) info.append(i.get(‘city’, ‘无’)) info.append(i.get(’education’, ‘无’)) info_list.append(info) return info_list获取所有数据了解了如何解析数据,剩下的就是连续请求所有页面了,我们构造一个函数来请求所有 30 页的数据。def main(): lang_name = ‘python’ wb = Workbook() conn = get_conn() for i in [‘北京’, ‘上海’, ‘广州’, ‘深圳’, ‘杭州’]: page = 1 ws1 = wb.active ws1.title = lang_name url = ‘https://www.lagou.com/jobs/positionAjax.json?city={}&needAddtionalResult=false'.format(i) while page < 31: info = get_json(url, page, lang_name) page += 1 import time a = random.randint(10, 20) time.sleep(a) for row in info: insert(conn, tuple(row)) ws1.append(row) conn.close() wb.save(’{}职位信息.xlsx’.format(lang_name))if name == ‘main’: main()完整代码import randomimport timeimport requestsfrom openpyxl import Workbookimport pymysql.cursorsdef get_conn(): ‘‘‘建立数据库连接’’’ conn = pymysql.connect(host=‘localhost’, user=‘root’, password=‘root’, db=‘python’, charset=‘utf8mb4’, cursorclass=pymysql.cursors.DictCursor) return conndef insert(conn, info): ‘‘‘数据写入数据库’’’ with conn.cursor() as cursor: sql = “INSERT INTO python (shortname, fullname, industryfield, companySize, salary, city, education) VALUES (%s, %s, %s, %s, %s, %s, %s)” cursor.execute(sql, info) conn.commit()def get_json(url, page, lang_name): ‘‘‘返回当前页面的信息列表’’’ headers = { ‘Host’: ‘www.lagou.com’, ‘Connection’: ‘keep-alive’, ‘Content-Length’: ‘23’, ‘Origin’: ‘https://www.lagou.com’, ‘X-Anit-Forge-Code’: ‘0’, ‘User-Agent’: ‘Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:61.0) Gecko/20100101 Firefox/61.0’, ‘Content-Type’: ‘application/x-www-form-urlencoded; charset=UTF-8’, ‘Accept’: ‘application/json, text/javascript, /; q=0.01’, ‘X-Requested-With’: ‘XMLHttpRequest’, ‘X-Anit-Forge-Token’: ‘None’, ‘Referer’: ‘https://www.lagou.com/jobs/list_python?city=%E5%85%A8%E5%9B%BD&cl=false&fromSearch=true&labelWords=&suginput=', ‘Accept-Encoding’: ‘gzip, deflate, br’, ‘Accept-Language’: ’en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7’ } data = {‘first’: ‘false’, ‘pn’: page, ‘kd’: lang_name} json = requests.post(url, data, headers=headers).json() list_con = json[‘content’][‘positionResult’][‘result’] info_list = [] for i in list_con: info = [] info.append(i.get(‘companyShortName’, ‘无’)) # 公司名 info.append(i.get(‘companyFullName’, ‘无’)) info.append(i.get(‘industryField’, ‘无’)) # 行业领域 info.append(i.get(‘companySize’, ‘无’)) # 公司规模 info.append(i.get(‘salary’, ‘无’)) # 薪资 info.append(i.get(‘city’, ‘无’)) info.append(i.get(’education’, ‘无’)) # 学历 info_list.append(info) return info_list # 返回列表def main(): lang_name = ‘python’ wb = Workbook() # 打开 excel 工作簿 conn = get_conn() # 建立数据库连接 不存数据库 注释此行 for i in [‘北京’, ‘上海’, ‘广州’, ‘深圳’, ‘杭州’]: # 五个城市 page = 1 ws1 = wb.active ws1.title = lang_name url = ‘https://www.lagou.com/jobs/positionAjax.json?city={}&needAddtionalResult=false'.format(i) while page < 31: # 每个城市30页信息 info = get_json(url, page, lang_name) page += 1 time.sleep(random.randint(10, 20)) for row in info: insert(conn, tuple(row)) # 插入数据库,若不想存入 注释此行 ws1.append(row) conn.close() # 关闭数据库连接,不存数据库 注释此行 wb.save(’{}职位信息.xlsx’.format(lang_name))if name == ‘main’: main()GitHub 地址:https://github.com/injetlee/Python/tree/master/%E7%88%AC%E8%99%AB%E9%9B%86%E5%90%88如果你想要爬虫获取的岗位信息,请关注公号【智能制造专栏】后台留言发送 “python岗位”。 ...

September 3, 2018 · 3 min · jiezi

前端使用puppeteer 爬虫生成《React.js 小书》PDF并合并

1、puppeteer 是什么?puppeteer: Google 官方出品的 headless Chrome node 库puppeteer github仓库puppeteer API官方介绍:您可以在浏览器中手动执行的大多数操作都可以使用Puppeteer完成!生成页面的屏幕截图和PDF。抓取SPA并生成预渲染内容(即“SSR”)。自动化表单提交,UI测试,键盘输入等。创建最新的自动化测试环境。使用最新的JavaScript和浏览器功能直接在最新版本的Chrome中运行测试。捕获时间线跟踪 您的网站,以帮助诊断性能问题。测试Chrome扩展程序。2、爬取网站生成PDF2.1 安装 puppeteer// 安装 puppeteer// 可能会因为网络原因安装失败,可使用淘宝镜像 // npm install -g cnpm –registry=https://registry.npm.taobao.orgnpm i puppeteer# or “yarn add puppeteer"2.2 《React.js小书》简介《React.js小书》简介 关于作者@胡子大哈这是⼀本关于 React.js 的⼩书。因为⼯作中⼀直在使⽤ React.js,也⼀直以来想总结⼀下⾃⼰关于 React.js 的⼀些知识、经验。于是把⼀些想法慢慢整理书写下来,做成⼀本开源、免费、专业、简单的⼊⻔级别的⼩书,提供给社区。希望能够帮助到更多 React.js 刚⼊⻔朋友。下图是《React.js 小书》部分截图:2.3 一些可能会用到的 puppeteer API// 新建 reactMiniBook.js, 运行 node reactMiniBook.js 生成pdfconst puppeteer = require(‘puppeteer’);(async () => { // 启动浏览器 const browser = await puppeteer.launch({ // 无界面 默认为true,改成false,则可以看到浏览器操作,目前生成pdf只支持无界面的操作。 // headless: false, // 开启开发者调试模式,默认false, 也就是平时F12打开的面版 // devtools: true, }); // 打开一个标签页 const page = await browser.newPage(); // 跳转到页面 http://huziketang.mangojuice.top/books/react/ await page.goto(‘http://huziketang.com/books/react/', {waitUntil: ’networkidle2’}); // path 路径, format 生成pdf页面格式 await page.pdf({path: ‘react.pdf’, format: ‘A4’}); // 关闭浏览器 await browser.close();})();知道这启动浏览器打开页面关闭浏览器主流程后,再来看几个API。const args = 1;let wh = await page.evaluate((args) => { // args 可以这样传递给这个函数。 // 类似于 setTimeout(() => {console.log(args);}, 3000, args); console.log(‘args’, args); // 1 // 这里可以运行 dom操作等js // 返回通过dom操作等获取到的数据 return { width: 1920, height: document.body.clientHeight, };}, args);// 设置视图大小await page.setViewport(wh);// 等待2sawait page.waitFor(2000);// 以iPhone X执行。const devices = require(‘puppeteer/DeviceDescriptors’);const iPhone = devices[‘iPhone X’];await page.emulate(iPhone);2.4 知道了以上这些API后,就可以开始写主程序了。简单说下:实现功能和主流程。从上面React.js小书截图来看。1、打开浏览器,进入目录页,生成0. React 小书 目录.pdf2、跳转到1. React.js 简介页面,获取左侧所有的导航a链接的href,标题。3、用获取到的a链接数组进行for循环,这个循环里主要做了如下几件事:3.1 隐藏左侧导航,便于生成pdf 3.2 给React.js简介等标题 加上序号,便于查看 3.3 设置docment.title 加上序号, 便于在页眉中使用。 3.4 隐藏 传播一下知识也是一个很好的选择 这一个模块(因为页眉页脚中设置了书的链接等信息,就隐藏这个了) 3.5 给 分页 上一节,下一节加上序号,便于查看。 3.6 最末尾声明下该pdf的说明,仅供学习交流,严禁用于商业用途。 3.7 返回宽高,用于设置视图大小 3.8 设置视图大小,创建生成pdf4、关闭浏览器具体代码:可以查看这里爬虫生成《React.js小书》的pdf每一小节的代码// node 执行这个文件// 笔者这里是:node src/puppeteer/reactMiniBook.js即可生成如下图:每一小节(0-46小节)的pdf生成这些后,那么问题来了,就是查看时总不能看一小节,打开一小节来看,这样很不方便。于是接下来就是合并这些pdf成为一个pdf文件。3、合并成一个PDF文件 pdf-merge起初,我是使用在线网站Smallpdf,合并PDF。合并的效果还是很不错的。这网站还是其他功能。比如word转pdf等。后来找到社区提供的一个npm packagepdf merge 。(毕竟笔者是写程序的,所以就用代码来实现合并了)这个pdf-merge依赖 pdftk安装 PDFtkWindows下载并安装笔者安装后,重启电脑才能使用。Debian, Ubuntu 安装笔者在Ubuntu系统安装后,即可使用。apt-get install pdftk使用例子const PDFMerge = require(‘pdf-merge’);const files = [ ${__dirname}/1.pdf, ${__dirname}/2.pdf,];// Buffer (Default)PDFMerge(files).then((buffer) => {…});// StreamPDFMerge(files, {output: ‘Stream’}).then((stream) => {…});// 笔者这里使用的是这个// Save as new filePDFMerge(files, {output: ${__dirname}/3.pdf}).then((buffer) => {…});知道这些后,可以开始写主程序了。简单说下主流程1、读取到生成的所有pdf文件路径,并排序(0-46)2、判断下输出文件夹是否存在,不存在则创建3、合并这些小节的pdf保存到新文件 React小书(完整版)-作者:胡子大哈-时间戳.pdf具体代码:可以查看这里爬虫生成《React.js小书》的pdf合并pdf的代码最终合并的pdf文件在这里React小书(完整版)-作者:胡子大哈,可供下载。本想着还可以加下书签和页码,没找到合适的生成方案,那暂时先不加了。如果读者有好的方案,欢迎与笔者交流。关于作者:常以轩辕Rowboat为名混迹于江湖。前端路上 | PPT爱好者 | 所知甚少,唯善学。个人博客segmentfault个人主页掘金个人主页知乎github小结1、puppeteer是Google 官方出品的 headless Chrome node 库,可以在浏览器中手动执行的大多数操作都可以使用Puppeteer完成。总之可以用来做很多有趣的事情。2、用 puppeteer 生成每一小节的pdf,用依赖pdftk的pdf-merge npm包, 合并成一个新的pdf文件。或者使用Smallpdf等网站合并。3、《React.js小书》,推荐给大家。爬虫生成pdf,应该不会对作者@胡子大哈有什么影响。作者写书服务社区不易,尽可能多支持作者。最后推荐几个链接,方便大家学习 puppeteer。puppeteer入门教程Puppeteer 初探之前端自动化测试爬虫生成ES6标准入门 pdf大前端神器安利之 Puppeteerpuppeteer API中文文档 ...

August 30, 2018 · 2 min · jiezi