模拟浏览器爬取网页
浏览器向服务器请求网页的过程:
1.浏览器发送请求
2.服务器发回响应
请求头和响应头包含了请求和响应信息,可以用谷歌浏览器在开发者工具中查看。响应代码ResponseCode反映了请求的执行情况
- ResponseCode为302:需要重定向
- ResponseCode为200:请求成功
1.用HttpUrlConnection或HtppsUrlConnection建立连接、进行通信。如果请求返回的ResponseCode为302,需要获取响应头中的重定向地址,带上服务器在响应头中设置的Cookie重新访问重定向地址,直到ResponseCode为200正常为止。
2.ResponseCode为200后,可以通过UrlConnection的getInputStream()方法获得输入流,从而获取html源码。
3.ResponseCode为200只表示请求成功,却不一定表示能获得真正的html网页。有些服务器发送的是带有js代码的html,用js代码再设置cookie,并且再次跳转(jump(),window.location="?")。这些js代码不一定是明文的,可能是用eval加密的。在这种情况下,我们可以解密这些js代码,分析它的行为(设置了哪些cookie,又跳转到何处)。然后在程序中模拟这些行为,从而获得真正的html源码。一般来说,一个普通站点的js加密方法和行为是固定的,分析几次后可以知道如何获取关键数据。
以下是代码。
- getRealHtml()获得真正的html源码,对于直接发送真实数据的站点来说,不需要这一步。
- 使用HttpsUrlConnection还是HttpURLConnection取决于访问的url地址使用Https协议还是Http协议
- 调用URLConnection的getResponseCode()或getInputStream()方法后,服务器已经返回响应,本次连接结束
- URLConnection一定要设置不立即重定向。因为需要在设置cookie后再重定向
import javax.net.ssl.HttpsURLConnection;
import java.io.*;
import java.net.*;
import java.text.SimpleDateFormat;
import java.util.Date;
public class HtmlCatcher {
//带cookie访问url地址,获取ResponseCode不为302后(即重定向后)的html源码
//对于初始cookie为空的访问,传入参数cookie为"",字符串空
public String getHTmlText(String url,String cookie){
BufferedReader in = null;
String result = "";
try{
HttpsURLConnection conn=getConnection(url);//获取一个UrlConnection
int code=0;
conn.setRequestProperty("Cookie", cookie);//设置请求头的cookie
while ((code=conn.getResponseCode())==302){
String redirect=conn.getHeaderField("Location");//获取重定向地址
String[] str=conn.getHeaderField("Set-Cookie").split(";");//获取响应头设置的cookie
for(int i=0;i<str.length-1;i++){
if(!cookie.contains(str[i]))
cookie+=str[i]+"; ";
}
conn=getConnection(redirect);
conn.setRequestProperty("Cookie", cookie);//带上cookie,返回while时访问重定向地址
}
//获取html源码
in = new BufferedReader(new InputStreamReader(conn.getInputStream(),"UTF-8"));
String line=null;
while((line = in.readLine())!=null){
result +="\n"+line;
}
}catch(Exception e){
//异常情况
e.printStackTrace();
if(e instanceof UnknownHostException){
//网络断开,5分钟后重试
Date date=new Date();
SimpleDateFormat sf=new SimpleDateFormat("yyyy-mm-dd HH:mm:ss");
System.out.println(sf.format(date)+" 网络断开。将在5分钟后重试");
try {
Thread.sleep(300000);
} catch (InterruptedException interruptedException) {
interruptedException.printStackTrace();
}
return getHTmlText(url, cookie);
}else if(e instanceof ConnectException){
//连接超时,立即重试
return getHTmlText(url,cookie);
}
return null;//发生其它异常时返回空
}finally{
try{
if(in!=null){
in.close();//关闭资源
}
}catch(IOException ex){
ex.printStackTrace();
}
}
return result;
}
//获取向urlStr所示地址的连接
public HttpsURLConnection getConnection(String urlStr){
URL url = null;
HttpsURLConnection conn = null;
try {
url = new URL(urlStr);
conn = (HttpsURLConnection) url.openConnection();
conn.setRequestProperty("User-Agent", "Chrome/71.0.3578.98 Safari/537.36");//设置客户端
conn.setRequestProperty("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");//设置文件类型
conn.setRequestProperty("Accept-Language", "zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3");//设置语言
conn.setRequestProperty("Accept-Encoding", "UTF-8");//设置编码
conn.setRequestProperty("Connection", "keep-alive");
conn.setRequestProperty("Upgrade-Insecure-Requests", "1");
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setInstanceFollowRedirects(false);//设置不要立即重定向。非常重要!!!!!!
} catch (IOException e) {
e.printStackTrace();
}
return conn;
}
//获取真正的html源码,对于一些站点来说不需要
public String getRealHtml(String url) {
String htmlText = getHTmlText(url, "");
//从第一次获得的htmlText分析出 redirect,cookie value,cookie key
...
//带上cookie再次访问,获取真正的html
//String html=getHTmlText(redirect,key+"="+value+";path=/;");
return html;
}
}
解密js,分析所设置cookie和跳转地址的一个例子:
- 原html代码,onLoad="javascript:jump()"表示页面加载时调用了jump()方法:
- js代码原来是一行
<!doctype html><html>
<head>
<meta charset="utf-8">
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control"
content="no-cache, no-store, must-revalidate">
<meta http-equiv="expires" content="0">
<script type="text/javascript">
//js原本是一行,可以用菜鸟工具尝试添加换行
eval(
function(p,a,c,k,e,r){
e=function(c){
return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))
};
if(!''.replace(/^/,String)){
while(c--)r[e(c)]=k[c]||e(c);
k=[function(e){return r[e]}];
e=function(){return'\\w+'};
c=1};
while(c--)
if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);
return p
}
(
'c 8={2:"t",f:"r"};6 d(){c a=3.2.k(/[a-j-9]{q}(?==)/g);s(a)w(c b=a.7;b--;)3.2=a[b]+"=0;l="+(m n(0)).o()}6 p(a){i 0<3.2.7&&(4=3.2.e(a+"="),-1!=4)?(4=4+a.7+1,5=3.2.e(";",4),-1==5&&(5=3.2.7),u(3.2.v(4,5))):""}6 h(a,b){3.2=a+"="+x(b)+";y=/;"}6 z(){d();h("A",8.2);B.C=8.f};',39,39,'6886868ccdeac86886868ccdeac8cookie6886868ccdeac8document6886868ccdeac8c_start6886868ccdeac8c_end6886868ccdeac8function6886868ccdeac8length6886868ccdeac8data6886868ccdeac86886868ccdeac86886868ccdeac86886868ccdeac8var6886868ccdeac8clearCookie6886868ccdeac8indexOf6886868ccdeac8uri6886868ccdeac86886868ccdeac8setCookie6886868ccdeac8return6886868ccdeac8f06886868ccdeac8match6886868ccdeac8expires6886868ccdeac8new6886868ccdeac8Date6886868ccdeac8toUTCString6886868ccdeac8getCookie6886868ccdeac8326886868ccdeac8https://www.IDontWantToShow.com6886868ccdeac8if6886868ccdeac8dce5c8305cb3140ae798fa328de49a926886868ccdeac8unescape6886868ccdeac8substring6886868ccdeac8for6886868ccdeac8escape6886868ccdeac8path6886868ccdeac8jump6886868ccdeac853dcd339779c66aa32fec592260eda0f6886868ccdeac8window6886868ccdeac8location'.split('6886868ccdeac8'),0,{}
)
)
</script>
</head>
<body onLoad="javascript:jump()">
</body></html>
- 解密后的js:
- 解密方法是在一个解密网页中输入待解密js代码进行解密。解密网页自行百度或见本文最后。
var data={
cookie:"dce5c8305cb3140ae798fa328de49a92",
uri:"https://www.IDontWantToShow.com"};
function clearCookie(){
var a=document.cookie.match(/[a-f0-9]{32}(?==)/g);
if(a)
for(var b=a.length;b--;)
document.cookie=a[b]+"=0;expires="+(new Date(0)).toUTCString()
}
function getCookie(a){
return 0<document.cookie.length&&(c_start=document.cookie.indexOf(a+"="),-1!=c_start)?
(c_start=c_start+a.length+1,c_end=document.cookie.indexOf(";",c_start),-1==c_end
&&(c_end=document.cookie.length),unescape(document.cookie.substring(c_start,c_end))):""}
function setCookie(a,b){
document.cookie=a+"="+escape(b)+";path=/;"
}
function jump(){
clearCookie();
setCookie("53dcd339779c66aa32fec592260eda0f",data.cookie);
window.location=data.uri};
通过解密后的js的jump()方法,可以知道,设置了cookie并且跳转。cookie的名称和其值、以及重定向地址的位置在原js中的位置如下:
'6886868ccdeac86886868ccdeac8cookie6886868ccdeac8document6886868ccdeac8c_start6886868ccdeac8c_end6886868ccdeac8function6886868ccdeac8length6886868ccdeac8data6886868ccdeac86886868ccdeac86886868ccdeac86886868ccdeac8var6886868ccdeac8clearCookie6886868ccdeac8indexOf6886868ccdeac8uri6886868ccdeac86886868ccdeac8setCookie6886868ccdeac8return6886868ccdeac8f06886868ccdeac8match6886868ccdeac8expires6886868ccdeac8new6886868ccdeac8Date6886868ccdeac8toUTCString6886868ccdeac8getCookie6886868ccdeac8326886868ccdeac8
https://www.IDontWantToShow.com //redirect url
6886868ccdeac8if6886868ccdeac8
dce5c8305cb3140ae798fa328de49a92 //cookie value
6886868ccdeac8unescape6886868ccdeac8substring6886868ccdeac8for6886868ccdeac8escape6886868ccdeac8path6886868ccdeac8jump6886868ccdeac8
53dcd339779c66aa32fec592260eda0f //cookie key
6886868ccdeac8window6886868ccdeac8location'.split('6886868ccdeac8')
经过分析,替换了.split()函数中的字符串后,重定向地址、cookie名称和值的位置是固定的,在程序中不需要解密js就可以获取。经验证,带cookie访问重定向地址获得了真正的、与用浏览器访问时相同的html。
- 附:解密网页源码。新建一个html文件,粘贴内容保存后打开使用。
<html>
<body>
<script>
a=62;
function encode() {
var code = document.getElementById('code').value;
code = code.replace(/[\r\n]+/g, '');
code = code.replace(/'/g, "\\'");
var tmp = code.match(/\b(\w+)\b/g);
tmp.sort();
var dict = [];
var i, t = '';
for(var i=0; i<tmp.length; i++) {
if(tmp[i] != t) dict.push(t = tmp[i]);
}
var len = dict.length;
var ch;
for(i=0; i<len; i++) {
ch = num(i);
code = code.replace(new RegExp('\\b'+dict[i]+'\\b','g'), ch);
if(ch == dict[i]) dict[i] = '';
}
document.getElementById('code').value = "eval(function(p,a,c,k,e,d){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)d[e(c)]=k[c]||e(c);k=[function(e){return d[e]}];e=function(){return'\\\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\\\b'+e(c)+'\\\\b','g'),k[c]);return p}("
+ "'"+code+"',"+a+","+len+",'"+ dict.join('|')+"'.split('|'),0,{}))";
}
function num(c) {
return(c<a?'':num(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36));
}
function run() {
eval(document.getElementById('code').value);
}
function decode() {
var code = document.getElementById('code').value;
code = code.replace(/^eval/, '');
document.getElementById('code').value = eval(code);
}
</script>
<textarea id=code cols=80 rows=20>
</textarea>
<input type=button onclick=encode() value="编码">
<input type=button onclick=run() value="执行">
<input type=button onclick=decode() value="解码">
</body>
</html>
下一篇: Linux内核学习(2)-系统调用