Git Product home page Git Product logo

goniub's Introduction

欢迎使用goniub 我带来的不仅仅是一个框架,最重要是做爬虫的**。

感谢大家的关注,因为各种原因文档迟迟未更新。但是还是有“识货”的人,进去看了goniub的源码。因为觉得好用所以给了star和fork,我再次由衷感谢大家对goniub的认可。

** goniub是一个java爬虫工具库,如果你想提高开发爬虫的效率,如果你用selenium老是被网站检测到机器识别,如果你想实现js注入。请你立马用goniub。**

添加maven依赖

<dependency>
  <groupId>com.deep007</groupId>
  <artifactId>goniub</artifactId>
  <version>2.0.2</version>
</dependency>

快速开始

不要直接使用HttpClient、OKHttp

我知道大家做java爬虫实现网络请求非常喜欢用Apache旗下的HttpClient或者OKHttp,但是我想告诉大家HttpClient直接使用的话,构造请求代码太多、太啰嗦。OKHttp比HttpClient体验好,但是在管理cookie层面体验又没有HttpClient好。毕竟OKHttp最初的设计是用来做服务层接口调用的,是你们用它来做爬虫。

我构造了自己的http请求库HttpDownloader, 来使用DefaultHttpDownloader发送一个请求
		DefaultHttpDownloader defaultHttpDownloader = new DefaultHttpDownloader();
		Page page = defaultHttpDownloader.download("https://www.example.com");
		page.getContent();//请求响应内容
		page.getContentType();//响应类型 对应 response header的content-type
		page.getOwnerUrl();//请求的url
		page.getRedirectUrl();//如果请过程中发生了302、303、304的重定向,getRedirectUrl()可以拿到重定向之后的真实url。这个功能是不是很击中了很多爬虫工程师的痛点?哈哈,继续......
		page.isRedirected();//是否发生过重定向。试问,其他的http库有没有专门为爬虫场景定义这样一个快捷的方法?没有吧。这就是爬虫工程师自己封装http库的好处,他知道你们平时用什么功能最多。把这些功能都搞成快捷方法了
		page.getResponseHeader();//得到Response的Header,Map<String, String>类型
		page.getStatus();//http响应代码,通常是200对吧
我要构造post、post json 、自定义header等参数的请求怎么搞?
		PageRequest request = PageRequestBuilder.custom()
		.header("custom header name", "v1")
		.header("custom header name2", "v2")
		.GBKEncoding() //告诉HttpDownloader到时候解析的强制用GBK方式进行解码,默认HttpDownloader解析查看Response.Content-type的编码或去html的mete标签中查找编码格式自动识别并解码。方法通常可以省略
		.UTF8Encoding()//告诉HttpDownloader到时候解析的强制用UTF-8方式进行解码。 方法通常可以省略
		.pageEncoding(PageEncoding.GB2312)//其他强制的编码通过这个方法指定。方法通常可以省略
		.isGet()//指定该请求为get,默认就是get请求。所以该方法可以省略
		.isPost()//指定该请求为post
		.param("postparamname1", "v1")//设置表单键值对,仅post请求生效
		.param("postparamname2", "v2")//设置表单键值对,仅post请求生效
		.httpsProxy(new HttpsProxy("127.0.0.1", 8080, "proxyuername", "proxypassword"))//单独为某个请求设置使用代理进行请求 带校验的账户名密码,这个直击爬虫工程师的痛点。所有的http库都是在httpclient层面进行全局设置代理,而实际爬虫是可能只需要对某些路径下的url使用代理。其他的路径不用,这样可以节省资源和提高抓取效率
		.httpsProxy(new HttpsProxy("127.0.0.1", 8080))//单独为某个请求设置使用代理进行请求 不带校验的账户名密码
		.postJSON(object)//如果为post json请求可以使用这个方法,他会将request.header.content-type设置为application/json; charset=utf-8,并且序列化object为json字符串,如果object本身就是string则不会进行json序列化
		.postText(body)//有些恶心的网络请求是post一个不规则的(既不是json、也不是urlencode)字符串body,他会将request.header.content-type设置为text/plain; charset=utf-8
		.httpEntity(mediaType, postBody)//如果postJSON postText还不满足你遇到恶心的奇怪请求请使用httpEntity自定义post实体
		.build();//构建pagerequest
		Page page = defaultHttpDownloader.download(request);
(爬虫观念普及)最大的高效的构造请求的方式是直接将curl命令解析成功PageRequest

大家做爬虫第一步是抓包对吧,然后分析抓包请求将请求用代码实现一遍。我不知道大家有没有去反思,就是我们每天为构造这些对网站来说“合法”的请求是不是有点浪费时间?我认为非常浪费时间,因为不管你用什么http库,你无非都是各种header、params、postbody上做各种get、set。抓包都用chrome或火狐,network选卡列表中的请求记录都可以进行copy as curl。我们通常验证请求都是用curl命令,如果能将curl直击解析成对应http库的request那么我们将节省大量时间。

CURLUtils的使用

这个curl的知乎的请求我管TMD的哪个参数是动态的哪个是静态的,我先用CURLUtils解析成PageRequest,放到HttpDownloader就可以发生合法的请求了。 作为爬虫工程师,你尽量把精力放在可变参数上,其他的套用curl模版就可以。

	//知乎某页面的curl
	String zhihuCommand = "curl 'https://www.zhihu.com/question/399149898/answer/1262844798' -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:56.0) Gecko/20100101 Firefox/56.0' -H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8' -H 'Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2' --compressed -H 'Referer: https://www.zhihu.com/' -H 'Connection: keep-alive' -H 'Cookie: _zap=d65e2855-f52a-4817-a2e1-6b9baac5f49c; d_c0=\"ACCmrPHAGBCPTlBVca50WongWu1r9-qghA8=|1569319098\"; Hm_lvt_98beee57fd2ef70ccdd5ca52b9740c49=1590215082,1590291119,1591272812; _ga=GA1.2.837698776.1582867010; capsion_ticket=\"2|1:0|10:1591272812|14:capsion_ticket|44:MjZmMThhZWI0MmYwNDgwMjhkODBlZTNhZGJjODJjMzE=|bde0488dcfcdfdd1349e5a5e86cdee1a4eed5ae4c7b8e7ccd19fe1d7c8675fe4\"; _xsrf=bb3DT7yoQTuHOF08LKCCymQad9lhPqaX; KLBRSID=031b5396d5ab406499e2ac6fe1bb1a43|1591272973|1591272810; Hm_lpvt_98beee57fd2ef70ccdd5ca52b9740c49=1591272963; SESSIONID=D3rjYUzS6blIto55L2SHzqmgRm7aU8ltfwDCUr4IhAB; _gid=GA1.2.854982874.1591272814; JOID=VloWBULuyHD6lChmf-yD4KkmyTJprPoHn8B7HSnVrB2270pVS4QpoqafL2J1wqCawBDW6JGAqYNsdwFH41E1_Yg=; osd=W14XBE3jzHH7myVifu2M7a0nyD1kqPsGkM1_HCjaoRm37kVYT4UoraubLmN6z6SbwR_b7JCBpo5odgBI7lU0_Ic=; z_c0=\"2|1:0|10:1591272919|4:z_c0|92:Mi4xUEhWVUFnQUFBQUFBSUthczhjQVlFQ1lBQUFCZ0FsVk4xelBHWHdCWC1MNFRnV185VVFYVWJPVlZXaUJCajNjZFZR|20a417b73fe617f9c3fd24ba1c3db87a5f468dab15cfffb3a8a6fdbb148a1c70\"; unlock_ticket=\"ABBKdHIjEgkmAAAAYAJVTd_s2F5M3g-f1vqOC3mOWiC4hngxaZiU5w==\"; tst=r; _gat_gtag_UA_149949619_1=1' -H 'Upgrade-Insecure-Requests: 1' -H 'Pragma: no-cache' -H 'Cache-Control: no-cache' -H 'TE: Trailers'";
	CURLUtils curlUtils = new CURLUtils(zhihuCommand);
	PageRequest request = curlUtils.createPageRequest();
	request.putParams("over write parama name 1", "v1");//如果是post请求,你可以添加或覆盖原有的参数
	request.putHeader("over write header name 1", "v1");//你可以添加或覆盖原有的header参数
	request.setUrl("https://www.zhihu.com/question/399149898/answer/2262844799");//重新定义url
隐藏浏览器指纹的selenium

原生的selenium是不支持隐藏浏览器指纹的,因为模拟浏览器有几十个特征可以被检测,仅仅隐藏 webdriver 这一个值是没有任何意义的。而因为“天生”具有隐藏指纹的Puppeteer/Pyppeteer出来之后,java的爬虫工程师们就纷纷涌向了Puppeteer的阵营。这对于喜爱selenium爬虫工程师来说是一个永远痛。不过selenium4.x出来之后新出了一个executeCdpCommand("Page.addScriptToEvaluateOnNewDocument", parameters)方法,它允许你在渲染每个网页之前执行给定的js脚本。利用这个特性我们就可以实现js前置抹除模拟浏览器指纹。详情请看贴“Selenium模拟浏览器如何正确隐藏特征” https://jishuin.proginn.com/p/763bfbd338cc

goniub已经把以上特征移植到了GoniubChromeDriver,实际上你只需要简单的api就可以享受具有隐藏浏览器指纹的webdriver

		GoniubChromeOptions.CHROME_DRIVER = "/Users/stephen/Downloads/chromedriver";
		GoniubChromeDriver hideMockerFeatureDriver = GoniubChromeDriver.newChromeInstance(false, false, null);
		hideMockerFeatureDriver.get("https://www.taobao.com");
		Object ret = hideMockerFeatureDriver.executeScript("return window.navigator.webdriver");
		System.out.println(ret);//null,成功隐藏指纹
		hideMockerFeatureDriver.quit();

而使用原生selenium的api,效果

		System.setProperty("webdriver.chrome.driver", "/Users/stephen/Downloads/chromedriver");
		ChromeDriver driver = new ChromeDriver();
		driver.get("https://www.taobao.com");
		Object ret = driver.executeScript("return window.navigator.webdriver");
		System.out.println(ret);//true,模拟浏览器指纹已被暴露
		driver.quit();
解决selenium无法得到全量的cookie

某爬虫工程师:selenium无法得到全量的cookie?这是什么问题?我用起来没问题啊。嗯,是的。如果你仅仅抓取单域名网站很可能遇不到这个问题,仅仅通过webDriver.manage().getCookies()这个方法就可以获取cookie。selenium无法得到全量的cookie的场景:

多域名网站群。实战场景举例:当年我在某互金公司做淘宝、支付宝网站用户信息抓取的项目的时候,用户会通过我们爬虫服务api传给我们账户/密码或者扫码我们劫持的淘宝登录二维码。这样我们服务器的爬虫就可以模拟完成用户登录,大家应该知道从淘宝的个人中心页面点击“账户余额”可以直接跳转到支付宝页面对吧!我们用selenium模拟点击账户余额跳转到支付宝,这时候支付宝给浏览器也种了支付宝登录状态的cookie。好,这时候浏览器相当于淘宝+支付宝的cookie我们都有了。但是当我们通过webDriver.manage().getCookies()获取cookie的时候。我们发现我们只能获取支付宝的cookie,而当前浏览器停留在支付宝页面*.alipay.com/xxx。当我们回到淘宝页面的时候再次webDriver.manage().getCookies()发现又只能获取taobao.com的cookie。先不追究webdriver的底层原因,我们按这个现象就可以知道selenium无法获取全量cookie。其实也很好理解为什么webdriver底层不愿意给你浏览器浏览过的全量cookie,因为selenium最初设计是做一个自动化网页测试工具,人家并不是给你做爬虫用的。是很多爬虫工程看到了selenium在爬虫场景的威力,才把它当成一个“爬虫工具”。其实网上也有人遇到过这样的问题 selenium_nofullcookies

大家都是怎么克服的?一般有以下三种方案

1、干脆直接不获取cookie了,从头到尾直接用selenium进行采集。(但是这样你就会很慢......,只要你的业务场景能够容忍你这样慢。OK!没问题!)

2、定制浏览器,基于webkit或者chromium开发自己爬虫浏览器。(这种方式,我觉得,你挺猛阿。c/c++玩的这么溜。最后大多数人都是失败告终,或者做出来的工具api体验度跟selenium比就跟屎一样。)

3、在selenium浏览器前面设置一个中间人攻击代理,因为中间人代理可以监控http(s)的发出request.headers和response.headers。从headers拦截cookie记录下来。(这种也是我一直用的方法,很投机取巧吧。付出最少的努力,就可以达到比方案二更稳定的效果。)

中间人代理获取全量cookie在goniub的实践

第一步是中间人代理技术选型,目前市面上的中间人代理有:mitmproxy(python)、LittleProxy(java)、Browsermob-Proxy(java)、等等还有其他更小众的。其中最mitmproxy是最稳定且功能最全。LittleProxy至今4年未更新。Browsermob-Proxy在LittleProxy的基础上进行了API封装,并支持Restful管理方式。但是自从LittleProxy不更新,Browsermob-Proxy也不更新了,使用起来有许多bug。所以mitmproxy就是goniub最好的选择。

第二步是跨语言集成。mitmproxy是python写的,goniub是服务于java爬虫工程师的项目。那么java如何驾驭python实现的mitmproxy。由此goniub采用了grpc + proto3实现java和python跨语言调用。而mitmproxy实例管理goniub引入了mitmproxy-hub的概念。就是一个python进程内可以开启多个mitmproxy实例,基于java通过grpc调用mitmproxy内部api进行实例管理。原理图如下: mitmproxy-hub架构图

第三步是模块化拆分。既然java中Browsermob-Proxy已经日落西山了,索性作者就把mitmproxy-java和单独拆分出来,跟着mitmproxy-hub一起。mitmproxy-hub其定义了和外部通信的proto3接口,今后方便继续扩展c++、golang、php等语言。作者并不能写一个像mitmproxy那样优秀的项目,但是他懂得用优雅的方式站在巨人的肩膀上。

获取selenium全量cookie示例步骤:

1、启动远端的mitmproxy-hub服务

git clone https://github.com/CreditTone/mitmproxy-hub.git
cd mitmproxy-hub/mitmproxy-hub/
python3 server.py//port of the service default on 60051

2、java项目依赖mitmproxy-java

		<dependency>
			<groupId>com.deep007</groupId>
			<artifactId>mitmproxy-java</artifactId>
			<version>1.0.6</version>
		</dependency>

3、准备就绪,发车......

	public static void main(String[] args) throws InterruptedException {
		RemoteMitmproxy remoteMitmproxy = new RemoteMitmproxy("127.0.0.1", 60051, "127.0.0.1", 8866);
		CookieCollectFilter cookieCollectFilter = new CookieCollectFilter();
		remoteMitmproxy.addFlowFilter(cookieCollectFilter);
		remoteMitmproxy.start();
		HttpsProxy httpsProxy = new HttpsProxy("127.0.0.1", 8866);
		System.out.println("启动浏览器");
		System.out.println("使用代理" + httpsProxy.getServer() + ":" + httpsProxy.getPort());
		GoniubChromeDriver chromeDriver = GoniubChromeDriver.newInstance(true, true, true, httpsProxy, GoniubChromeOptions.CHROME_USER_AGENT);
		chromeDriver.get("https://www.taobao.com");
		//登录逻辑省略。。。。。。。阿巴阿巴
		//跳转到支付宝代码省略。。。。。。阿巴阿巴
		for (Cookie cookie : cookieCollectFilter.catchCookies) {
			System.out.println(cookie.getDomain() + ">>>"+ cookie.getName()+"="+cookie.getValue() +" path:"+cookie.getPath());
		}
		//此处将打印浏览器历史sent和receive所有的cookie
		remoteMitmproxy.stop();
		chromeDriver.quit();
}

goniub's People

Contributors

credittone avatar dependabot[bot] avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

goniub's Issues

bug

填充文字只能填充两个以上要不然会报bound must be positive
源码中有这个 random.nextInt(text.length() / 3);

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.