目录
近期,有人将本人博客,复制下来,直接上传到百度文库等平台。
本文为原创博客,仅供技术学习使用。未经允许,禁止将其复制下来上传到百度文库等平台。如有转载请注明本文博客的地址(链接)
源码请联系邮箱:[email protected]
本人已经写了几篇模拟登陆的程序,具体请看http://blog.csdn.net/qy20115549/article/details/52250022
http://blog.csdn.net/qy20115549/article/details/52249232这两篇博客
在写网络爬虫时,有时需要模拟登陆。其实模拟登陆很简单,只需要通过抓包,然后使用httpclient输入用户名,密码等相关参数即可。
但也有一些复杂的,比如说豆瓣,微博等网站,在模拟登陆时,需要输入验证码,这一点比较烦人。如下,是豆瓣登陆时,需要输入验证码。
如下图所示,通过抓包,我们可以看出在登陆时,需要输入的参数有哪些。将下面对应的相关参数,写入到程序中,便可以模拟登陆了。
一般解决验证码问题的思路是:将网站验证码对应的图片下载下来,以手工输入的方式,进行登陆。登陆完成后,便可以给一个url或多个url,爬取该url对应的html或json数据对应的信息。注意在爬取多个url的时候,只需要一次登陆即可,这样避免了多次输入验证码图片
刷新验证码,进行图片请求抓包,我们可以看到,每次刷新,都会有一个图片id产生。如下图所示:
{"url":"\/\/www.douban.com\/misc\/captcha?id=lr50o304pwf8KvVdsdL7MKLU:en&size=s","token":"lr50o304pwf8KvVdsdL7MKLU:en","r":false}
可以基于此地址获取图片地址:
https://www.douban.com/misc/captcha?id=RI1ko1Zla7ZdvXYvwebIo0ia:en&size=s
如下,以豆瓣为例,进行登陆,并获取只有登陆之后,才能看到的信息
package simulate;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.BasicResponseHandler;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.json.JSONException;
import org.json.JSONObject;
import util.HTTPUtils;
/**
* @author:合肥工业大学 管理学院 钱洋
* @email:[email protected]
* @
*/
public class DoubanSimulate {
private static HttpClient httpClient=new DefaultHttpClient();
//主登录入口
public static void loginDouban(){
// String redir="https://www.douban.com/people/144537495/";
String login_src="https://accounts.douban.com/login";
//输入用户名及密码
String form_email="";
String form_password="";
//获取验证码
String captcha_id=getImgID();
String login="登录";
String captcha_solution="";
//输入验证码
System.out.println("请输入验证码:");
BufferedReader buff=new BufferedReader(new InputStreamReader(System.in));
try {
captcha_solution=buff.readLine();
} catch (IOException e) {
e.printStackTrace();
}
//构建参数,即模拟需要输入的参数。这部分通过抓包获得。不会抓包的请看我之前写的一些博客
List list=new ArrayList();
list.add(new BasicNameValuePair("form_email", form_email));
list.add(new BasicNameValuePair("form_password", form_password));
list.add(new BasicNameValuePair("captcha-solution", captcha_solution));
list.add(new BasicNameValuePair("captcha-id", captcha_id));
list.add(new BasicNameValuePair("login", login));
HttpPost httpPost = new HttpPost(login_src);
try {
//向后台请求数据,登陆网站
httpPost.setEntity(new UrlEncodedFormEntity(list));
HttpResponse response=httpClient.execute(httpPost);
HttpEntity entity=response.getEntity();
String result=EntityUtils.toString(entity,"utf-8");
} catch (ClientProtocolException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* 获取验证码图片“token”值
* @return token
*/
private static String getImgID(){
//Json的地址[数据中包含验证码的地址]
String src="https://www.douban.com/j/misc/captcha";
HttpGet httpGet=new HttpGet(src);
String token="";
try {
HttpResponse response=httpClient.execute(httpGet);
HttpEntity entity=response.getEntity();
//将json数据转化为map,对应的是key,value的形式。不理解json数据的,请看我前面的关于json解析的博客
String content=EntityUtils.toString(entity,"utf-8");
Map mapList=getResultList(content);
token=mapList.get("token");
//获取验证码的地址
String url="https:"+mapList.get("url");
//下载验证码并存储到本地
downImg(url);
//System.out.println(token);
//System.out.println(url);
//System.out.println(content);
} catch (ClientProtocolException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return token;
}
/**
* 用JSON 把数据格式化,并生成迭代器,放入Map中返回
* @param content 请求验证码时服务器返回的数据
* @return Map集合
*/
public static Map getResultList(String content){
Map maplist=new HashMap();
try {
JSONObject jo=new JSONObject(content.replaceAll(",\\\"r\\\":false", ""));
Iterator it = jo.keys();
String key="";
String value="";
while(it.hasNext()){
key=(String) it.next();
value=jo.getString(key);
maplist.put(key, value);
}
} catch (JSONException e) {
e.printStackTrace();
}
return maplist;
}
/**
* 此方法是下载验证码图片到本地
* @param src 给个验证图片完整的地址
* @throws IOException
*/
private static void downImg(String src) throws IOException{
File fileDir=new File("E:\\钱洋个人\\IdentifyingCode");
if(!fileDir.exists()){
fileDir.mkdirs();
}
//图片下载保存地址
File file=new File("E:\\钱洋个人\\IdentifyingCode\\yzm.png");
if(file.exists()){
file.delete();
}
InputStream input = null;
FileOutputStream out= null;
HttpGet httpGet=new HttpGet(src);
try {
HttpResponse response=httpClient.execute(httpGet);
HttpEntity entity = response.getEntity();
input = entity.getContent();
int i=-1;
byte[] byt=new byte[1024];
out=new FileOutputStream(file);
while((i=input.read(byt))!=-1){
out.write(byt);
}
System.out.println("图片下载成功!");
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
out.close();
}
//登陆后,便可以输入一个或者多个url,进行请求
public static String gethtml(String redirectLocation) {
HttpGet httpget = new HttpGet(redirectLocation);
// Create a response handler
ResponseHandler responseHandler = new BasicResponseHandler();
String responseBody = "";
try {
responseBody = httpClient.execute(httpget, responseHandler);
} catch (Exception e) {
e.printStackTrace();
responseBody = null;
} finally {
httpget.abort();
// httpClient.getConnectionManager().shutdown();
}
return responseBody;
}
//一个实例,只请求了一个url
public static void main(String[] args) throws ClientProtocolException, IOException {
loginDouban();
String redir="https://www.douban.com/people/conanemily/contacts";
// System.out.println(gethtml(redir));
String cc=gethtml(redir);
System.out.println(cc);
}
}
关于为什么使用此架构,可以去看我前面写的网络爬虫框架。此程序主要是用来爬取类似下面这一个网址的信息。
https://www.douban.com/people/conanemily/contacts。包含的信息有:用户自身的id,用户对应好友的id。将这两个字段写入model
package model;
/**
* @author:合肥工业大学 管理学院 钱洋
* @email:[email protected]
* @
*/
public class DouBanUserUrl {
private String user_id;
private String user_contactsid;
public String getUser_id() {
return user_id;
}
public void setUser_id(String user_id) {
this.user_id = user_id;
}
public String getUser_contactsid() {
return user_contactsid;
}
public void setUser_contactsid(String user_contactsid) {
this.user_contactsid = user_contactsid;
}
}
这里是main方法,其实,我注释已经写的非常详细了。大家可以直接看程序。
main方法里调用了上面的模拟登陆的程序,及给定url,模拟登陆后获取html的程序。
package navi.main;
import java.io.IOException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import db.MYSQLControl;
import model.DouBanUserUrl;
import parse.DouBanParse;
import simulate.DoubanSimulate;
/**
* @author:合肥工业大学 管理学院 钱洋
* @email:[email protected]
*/
public class DouBan {
static final Log logger = LogFactory.getLog(DouBan.class);
public static void main(String[] args) throws IOException, SQLException, InterruptedException {
//以一个为起点
/*List DouBanUserUrlData=new ArrayList();
DoubanSimulate.loginDouban();
String id="/conanemily/";
String redir="https://www.douban.com/people"+id+"contacts";
String html=DoubanSimulate.gethtml(redir);
DouBanUserUrlData=DouBanParse.getData(id,html);
if (DouBanUserUrlData.size()!=0) {
MYSQLControl.executeUpdate(DouBanUserUrlData);
}*/
//多个url,循环继续
List DouBanUserUrlData=new ArrayList();
//登陆网站
DoubanSimulate.loginDouban();
//从数据库获取需要爬取的用户id
List DouBanUserUrlDatafromMYSQL=MYSQLControl.getListInfoBySQL("select user_id,user_contactsid from doubancontacts where tag=0",DouBanUserUrl.class);
System.out.println(DouBanUserUrlDatafromMYSQL.get(1).getUser_id());
int count=1;
//对需要爬取的每一个用户进行循环
for (DouBanUserUrl ids:DouBanUserUrlDatafromMYSQL) {
String id=ids.getUser_contactsid();
++count;
//待爬取的用户url
String redir="https://www.douban.com/people"+id+"contacts";
//防止爬虫被封杀,需要中间休息
if(count%2==0){
//产生随机数
int m = (int)(Math.random()*10);
Thread.sleep(m*1000);
}
logger.info("count:"+count+"+\tthe current crawl url is:"+redir);
//获取html信息
String html=DoubanSimulate.gethtml(redir);
//解析html信息
DouBanUserUrlData=DouBanParse.getData(id,html);
//存储获取的信息
if (DouBanUserUrlData.size()!=0) {
MYSQLControl.executeUpdatecontainId(DouBanUserUrlData,id);
}
}
}
}
这部分是针对获取的html内容进行解析,获取用户好友的id。这要使用的方法很简单,就是Jsoup。
package parse;
import java.util.ArrayList;
import java.util.List;
import model.DouBanUserUrl;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
/**
* @author:合肥工业大学 管理学院 钱洋
* @email:[email protected]
*/
public class DouBanParse {
//将需要返回的数据,保存到List中
public static List getData (String user_id,String html) {
List DouBanUserUrlData=new ArrayList();
Document doc=Jsoup.parse(html);
Elements elements=doc.select("dl[class=obu]");
for (Element ele:elements) {
String user_contactsid=ele.select("dd").select("a").attr("href").replaceAll("https://www.douban.com/people", "");
DouBanUserUrl douBanUserUrl=new DouBanUserUrl();
douBanUserUrl.setUser_id(user_id);
douBanUserUrl.setUser_contactsid(user_contactsid);
DouBanUserUrlData.add(douBanUserUrl);
}
return DouBanUserUrlData;
}
}
db中包含两个java文件,MyDataSource,MYSQLControl。这两个文件的作用已在前面说明了。
package db;
import javax.sql.DataSource;
import org.apache.commons.dbcp2.BasicDataSource;
/**
* @author:合肥工业大学 管理学院 钱洋
* @email:[email protected]
*/
public class MyDataSource {
public static DataSource getDataSource(String connectURI){
BasicDataSource ds = new BasicDataSource();
//MySQL的jdbc驱动
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUsername("root"); //所要连接的数据库名
ds.setPassword("112233"); //MySQL的登陆密码
ds.setUrl(connectURI);
return ds;
}
}
这里我写了很多的操作方面,本程序里面用到两个方法MYSQLControl.getListInfoBySQL()及MYSQLControl.executeUpdatecontainId()。其他方法,大家可以忽略不看,仅供大家写其他程序的时候参考。
package db;
import java.sql.SQLException;
import java.util.List;
import javax.sql.DataSource;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.ResultSetHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.ColumnListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import model.ContentModel;
import model.DouBanUserUrl;
/**
* @author:合肥工业大学 管理学院 钱洋
* @email:[email protected]
*/
public class MYSQLControl {
static final Log logger = LogFactory.getLog(MYSQLControl.class);
static DataSource ds = MyDataSource.getDataSource("jdbc:mysql://127.0.0.1:3306/moviedata");
static QueryRunner qr = new QueryRunner(ds);
//第一类方法
public static void executeUpdate(String sql){
try {
qr.update(sql);
} catch (SQLException e) {
logger.error(e);
}
}
//按照SQL查询单个结果
public static Object getScalaBySQL ( String sql ){
ResultSetHandler