某天,我发现我的文章被某个用户点赞了。欣喜之下,把那边文章重新校核更改一遍,接着进入这个点赞我的用户看看,结果发现他近期的博客是一些爬虫的实战。我想,我那篇文章,应该是他做的一个小程序批量加载用户信息并给文章点赞。我觉得这是一个有点意思的事,于是用java实现csdn批量爬取用户名并点赞。其中批量爬取用户名的思路借鉴这位大神的这篇博文GO+Selenium批量关注CSDN 1 (如何获取100万用户名, ES去重)。
题外话到此为止,先说说怎么获得csdn的用户名的,他的方法是通过用户的粉丝和关注两个列表,最多可以获得12个用户名,再根据这12个用户名,继续进入每个用户的主页,获得该用户的粉丝和关注列表的用户数据,这样无限循环往复,直到找到很多很多的用户名。
1.通过https://me.csdn.net/用户名
获得粉丝和关注列表。
2.查看页面源码,用正则匹配
qq_33981796
匹配正则
a class="fans_title" href="https://me.csdn.net/(.+)"
3.获得匹配的用户名,再次进入https://me.csdn.net/用户名
页面,重复1,2的操作
代码
public Set users = new HashSet<>();
public void getCSDNUsers(String userName) {
if (users.size() > 30) {
return;
}
Set userList = parseFans(mePage(userName));
if (userList != null && userList.size() > 0) {
synchronized (users) {
users.addAll(userList);
}
userList.stream().forEach(user -> getCSDNUsers(user, token));
}
}
public String mePage(String username) {
String follerUrl = "https://me.csdn.net/" + username;
try {
Map headers = new HashMap<>();
headers.put("cook", "UserName=luo4105; UserInfo=de5e709f7cd84e2d87648d45e6288db0; " +
"UserToken=de5e709f7cd84e2d87648d45e6288db0;");
InputStream inputStream = HttpUtil.doGet(follerUrl, headers);
String response = StreamUtil.inputStreamToString(inputStream, "UTF-8");
return response;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
public Set parseFans(String pageContext) {
if (pageContext == null || pageContext.length() == 0) {
return new HashSet<>();
}
String fansRegex = "a class=\"fans_title\" href=\"https://me.csdn.net/(.+)\"";
Pattern pattern = Pattern.compile(fansRegex);
Matcher matcher = pattern.matcher(pageContext);
Set fanses = new HashSet<>();
while (matcher.find()) {
fanses.add(matcher.group(1));
}
return fanses;
}
这里只拉30个用户的数据,我也没有想好怎么截止这个任务。附上自己写的httpUtil和StreamUtil的访问网页的方法并输出string的方法。
//httpUtil.doGet方法
public static InputStream doGet(String urlstr, Map headers) throws IOException {
URL url = new URL(urlstr);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 " +
"(KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36");
conn.setRequestProperty("accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp," +
"image/apng,*/*;q=0" +
".8");
if (headers != null) {
Set keys = headers.keySet();
keys.stream().forEach(key -> {
conn.setRequestProperty(key, headers.get(key));
});
}
Random random = new Random();
String ip =
(random.nextInt(100) + 100) + "." + (random.nextInt(100) + 100) + "." + (random.nextInt(100) + 100) + "." + (random.nextInt(100) + 100);
conn.setRequestProperty("x-forwarded-for", ip);
InputStream inputStream = conn.getInputStream();
return inputStream;
}
public class StreamUtil {
public static String inputStreamToString(InputStream is, String charset) throws IOException {
byte[] bytes = new byte[1024];
int byteLength = 0;
StringBuffer sb = new StringBuffer();
while ((byteLength = is.read(bytes)) != -1) {
sb.append(new String(bytes, 0, byteLength, charset));
}
return sb.toString();
}
}
分析一下博文点赞的API,随便打开一篇博客,并点赞,查看浏览器请求日志。
- Request URL: https://blog.csdn.net/u014633966/phoenix/article/digg?ArticleId=86526464
- Request Method: GET
cookie: UserName=luo4105; UserToken=xxxxxxxxxxxx;
只要拿到ArticleId,自己的UserToken,就可以拼接请求点赞博客了。
大致思路是这样的,打开https://me.csdn.net/用户名
博客主页,解析文章列表便可获得文章id。跟踪登陆API,查看请求参数和响应中的cook的插入,解析出token。有了这些参数,就可以拼点赞请求了。
为了防止一下子把羊毛褥完了,我这里拿的是每个用户下的第一篇aritcleId,思路是这样的。
1.进入该用户的默认博客列表页面
这里通过https://blog.csdn.net/用户名/article/list/1
进入博客列表
2.通过正则匹配获得第一篇文章链接中的ArticleId和userName
页面代码
原
Could not set parameters for mapping错误与mybatis源码追踪
实现代码
public Map getFirstArticleId(Set userNames) {
Map articleIdMap = new HashMap<>();
String blogUrlTemplate = "https://blog.csdn.net/%s/article/list/1";
userNames.stream().forEach(userName -> {
String blogUrl = String.format(blogUrlTemplate, userName);
try {
InputStream inputStream = HttpUtil.doGet(blogUrl);
String content = StreamUtil.inputStreamToString(inputStream, "UTF-8");
String articleIdRegex = "https://blog.csdn.net/" + userName + "/article/details/([0-9]{8})";
Pattern pattern = Pattern.compile(articleIdRegex);
Matcher matcher = pattern.matcher(content);
while (matcher.find()) {
articleIdMap.put(userName, matcher.group(1));
break;
}
} catch (IOException e) {
System.out.println("http get failed");
e.printStackTrace();
}
});
return articleIdMap;
}
使用浏览器查看csdn登陆请求,分析request和response,找到接口url,请求格式,用户名和密码在哪里传的,token在哪里返回的。
{loginType: "1", pwdOrVerifyCode: "xx", userIdentification: "xxx",...}
,关键参数是pwdOrVerifyCode(密码)和userIdentification(账号)。实现代码
/**
* 模拟登陆,并从cookie中拿出token和用户名
*
* @param username
* @param password
*/
public UserToken login(String username, String password) {
String url = "https://passport.csdn.net/v1/register/pc/login/doLogin";
Map headers = new HashMap<>();
headers.put("Content-Type", "application/json");
headers.put("accept", "application/json;charset=UTF-8");
JSONObject jsonObject = new JSONObject();
jsonObject.put("loginType", "1");
jsonObject.put("pwdOrVerifyCode", password);
jsonObject.put("userIdentification", username);
UserToken userToken = new UserToken();
try {
Map> repHeaders = HttpUtil.doPostJSON(url, headers,
jsonObject.toJSONString());
List setCookies = repHeaders.get("Set-Cookie");
if (setCookies != null && setCookies.size() > 0) {
setCookies.stream().forEach(cook -> {
if (cook.contains("UserToken=")) {
String userTokenRegex = "UserToken=(.*); Max";
Pattern pattern = Pattern.compile(userTokenRegex);
Matcher matcher = pattern.matcher(cook);
while (matcher.find()) {
userToken.setToken(matcher.group(1));
}
}
if (cook.contains("UserName=")) {
String userTokenRegex = "UserName=(.*); Max";
Pattern pattern = Pattern.compile(userTokenRegex);
Matcher matcher = pattern.matcher(cook);
while (matcher.find()) {
userToken.setUserName(matcher.group(1));
}
}
});
}
} catch (IOException e) {
e.printStackTrace();
}
return userToken;
}
@Data
public static class UserToken {
private String token;
private String userName;
}
以上,就是csdn自动爬取用户并给文章点赞的实现方式和过程。
整个项目源代码已经上传github,欢迎大家下载使用并改进,https://github.com/programluo/csdn-tool 。