java并发爬取 腾讯应用宝 里的数据 (含有源码下载)

今天想要做的是把应用宝网站数据爬取下来。
知识要点

  • 解析html
  • 解析json
  • 线程池执行

爬取步骤

  • 左边一栏是分类,右边是应用app数据。
  • 首先解析左边一栏的数据,在html中class为menu-junior的li标签里。
  • 那么我们要解析这个li标签,拿到应用的大类,然后根据大类再爬取数据。

解析提取html对应的数据

全局变量存放这个应用首页的地址和发送json请求的地址

public static String mainPageUrl = 
	"https://sj.qq.com/myapp/category.htm?orgame=1";

public static String jsonUrl = 
	"https://sj.qq.com/myapp/cate/appList.htm?orgame=1&categoryId=
				{categoryId}&pageSize=20&pageContext={pageContext}";

发送http get请求,使用jsoup解析这个地址。

String content = HTTPUtil.sendGet(mainPageUrl).getContent();
Document document = Jsoup.parse(content);
Elements elements = document.getElementsByClass("menu-junior");
Elements tags = elements.get(0).getElementsByTag("li");
//循环li标签集合,拿到categoryId
for(int i=0; i<tags.size(); i++) {
    String htmlId = tags.get(i).attr("id");
    if(!StringUtils.isEmpty(htmlId)) {
        String categoryId = htmlId.replace("cate-", "");
        categoryIdList.add(categoryId);
    }
}

categoryIdList存放大类的线程安全的队列。因为LinkedBlockingQueue实现是线程安全的,实现了先进先出等特性,是作为生产者消费者的首选。

private static ConcurrentLinkedQueue<String> categoryIdList = 
	new ConcurrentLinkedQueue<>();

jsop对应的pom文件

<dependency>
	<groupId>org.jsoupgroupId>
	<artifactId>jsoupartifactId>
	<version>1.10.3version>
dependency>

<dependency>
	<groupId>net.sourceforge.htmlunitgroupId>
	<artifactId>htmlunitartifactId>
	<version>2.25version>
dependency>

并发爬取,以大类为一个线程任务

这里有22个大类,那么开启22个任务,开启6个线程池去轮询这22个任务。
实现思路是: 每个线程从队列中消费一个大类数据,以这个大类为参数发送http请求。

url的内容是。{categoryId}和{pageContext}是后面的传递参数。

https://sj.qq.com/myapp/cate/appList.htm?orgame=1&categoryId
={categoryId}&pageSize=20&pageContext={pageContext}

处理url发送的请求

url = url.replace("{categoryId}", categoryId).replace("{pageContext}", "0");
//发送http请求,解析json串
String content = HTTPUtil.sendGet(url).getContent();
JSONObject jsonObject = JSON.parseObject(content);
String objString = jsonObject.getString("obj");

//每一次请求,是分页进行的。为了获取全部数据,所以还要进行分页处理,每次pageSize增加,直至取到空数据为止。
int index = 0;
do {
    JSONArray jsonArray = jsonObject.getJSONArray("obj");
    for(int i=0; i<jsonArray.size(); i++) {
        App app = jsonArray.getJSONObject(i).toJavaObject(App.class);
        
        //-------获得app的数据,对app数据进行操作。----//
        System.out.println(i+"  "+app.getAppName() +"   "+url);
		//-----------------------------------------//
		
    }
    //每执行一次pageContext加上20
    int pageContext = (++index)*20;
    String againUrl = jsonUrl;
    //解析url,替换pageContext
    againUrl = againUrl.replace("{categoryId}", categoryId)
    .replace("{pageContext}", pageContext+"");
    content = HTTPUtil.sendGet(againUrl).getContent();
    jsonObject = JSON.parseObject(content);
    objString = jsonObject.getString("obj");
} while (!StringUtils.isEmpty(objString));

有了上面的方法后,那么就可以使用线程池调用他。

public void run() {
 //开启线程池去处理数据
 while(!CollectionUtils.isEmpty(categoryIdList)) {
     final String categoryId = categoryIdList.poll();
     executorService.submit(new Runnable() {
         @Override
         public void run() {
             sendJsonUrl(jsonUrl, categoryId);
         }
     });
 }
 //线程池执行完毕后,关闭退出线程池
 executorService.shutdown();
}

如果了解获得线程池分配得详解,欢迎点击查看

上面可能写的比较乱,这里有提供源码下载。
水平原因可能存在错误,希望指正 [email protected]

你可能感兴趣的:(并发编程)