网络爬虫实现-拔取数据为bangumi动画排行榜(共4586部动画)
以bangumi动画为例= =
成果,总共四千多个(最后一名是雷锋的故事)
这个图是我给扒下来的数据保存到mysql数据库了在导出的excel文件
还有用IO流输出的txt文件和DOM转换的xml文件就懒得放了
废话不多说,上代码
Anime类
private int id;//排名
private String name;//名字
private String oldName;//原名
private String message;//信息
private double rating;//分数
private int number;//评分人数
private String imagePath;//图片地址
还有getset方法和toString方法,就不贴了
完成思路:
按照bangumi动画排行榜的域名,设定的符合查找标准的for循环。
其域名的标识是直接从1-191顺序排列,故建立一个从1开始循环到191的for循环。
建立一个只有单个线程的线程池,虽然速度比多线程有所降低,但是由于其只有一个线程,所以会按照顺序从1-191页读取数据
避免了多线程导致的顺序混乱问题。
根据面向对象的思想,咱是个指挥官,你怎么完成是你的事情,只要你能达到效果,我只管调用方法就是了
我需要的结果就是,这个for循环给我转完后,我的List集合里就存了[bangumi动画排行榜里的所有的动画对象]
直接让线程池中的线程执行写好了的AnimeSpider线程,把要扒的域名、接收数据的集合、闭锁给传过去。完事。
List集合拿到了,也就是四千多个动画对象拿到了,然后就可以随意操作了
调用我写好了的WriteAnimeTXT.writeAnime(animes);WriteAnimeXML.writeAnime(animes);
MybaseInsert.insert(animes);writeImage(animes);这些我写好了的方法,做到输出为txt、xml、保存到数据库、下载图片等等
方法我都写进来了,共四页
解释下闭锁:
CountDownLatch类,创建时可以指定一个int类型的数字,在数字到0之前,await()将一直阻塞,使程序停止在这个位置。直到数字归零
在这使用是为了让线程都走完,也就是说要让List集合里已经存了bangumi动画排行的所有数据后,在执行后面的方法
使用CountDownLatch中的方法countDown()可以使数字减一
主类
package bangumi; import java.io.BufferedWriter; import java.io.FileWriter; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class TestAnime { public static void main(String[] args) { System.out.println("开始检索bangumi动画排行榜..."); //要传的集合 List<Anime> animes = new ArrayList<Anime>(); //闭锁 CountDownLatch cdl = new CountDownLatch(191); //单个线程的线程池,为了集合中的数据顺序排列 ExecutorService pool = Executors.newSingleThreadExecutor(); for(int i = 1 ; i <= 191 ;i++){ String url = "http://bangumi.tv/anime/browser?sort=rank&page="+i; pool.execute(new AnimeSpider(url,animes,cdl)); } //关池子 pool.shutdown(); try { //开锁 cdl.await(); System.out.println("检索bangumi动画排行榜完毕,已全部储存..."); System.out.println("正在准备输出所有排行信息:"); System.out.print("5\t"); Thread.sleep(1000); System.out.print("4\t"); Thread.sleep(1000); System.out.print("3\t"); Thread.sleep(1000); System.out.print("2\t"); Thread.sleep(1000); System.out.print("1\t"); Thread.sleep(1000); //遍历 for (Anime a : animes) { System.out.println(a); } //写入txt文件 //WriteAnimeTXT.writeAnime(animes); //写入XML文件 //WriteAnimeXML.writeAnime(animes); //写入数据库 //MybaseInsert.insert(animes); //下载图片 //writeImage(animes); } catch (InterruptedException e) { e.printStackTrace(); } } //下载图片的方法 public static void writeImage(List<Anime> animes){ ExecutorService pool = Executors.newFixedThreadPool(10); for (Anime a : animes) { //调用ImgLoader线程传个Anime对象过去 pool.execute(new ImgLoader(a)); } pool.shutdown(); } }
AnimeSpider类,即在主方法中new了191次的线程
package bangumi; import java.io.IOException; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.regex.Matcher; import java.util.regex.Pattern; //这些是json包中的!! import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.select.Elements; public class AnimeSpider implements Runnable { private String url;//接受的地址 private List<Anime> animes;//接受的集合 private CountDownLatch cdl;//接受的闭锁 private static int id = 0;//id,id之所以没有去网页中获取,因为让他每次自动增长是和网页中扒下来的一样的 public AnimeSpider() { super(); } public AnimeSpider(String url) { super(); this.url = url; } public AnimeSpider(String url, List<Anime> animes, CountDownLatch cdl) { super(); this.url = url; this.animes = animes; this.cdl = cdl; } @Override public void run() { try { Document doc = Jsoup.connect(url).get();//通过传进来的url获取网页的html文档对象 //System.out.println(doc);这里可以直接打印出整个网页的html Elements elems = doc.select(".browserFull li");//获取根节点,查找范围为:class = “browserFull”的标签中的<li>标签 for(Element e:elems){ String name = e.select("a").text();//查找范围为: <a>标签中的文本标签 String oldName = e.select(".grey").text();//class = “grey”的标签中的文本标签 String message = e.select("p").first().text();//<p>标签中的第一个的文本标签 Double rating = new Double(e.select(".fade").text()); String number = e.select(".tip_j").text(); String imgPath = e.select(".cover").attr("src");//class = “cover”的标签中的属性为 src的内容 //这一段是将评分人数中的数字提取出来,保存到int类型中,因为提取出来的评分人数是这样的:(****人评分) String str = number; String regEx="[^0-9]"; Pattern p = Pattern.compile(regEx); Matcher m = p.matcher(str); number = m.replaceAll(""); int num = new Integer(number); //把数据传到anime对象里,在塞到集合中去 animes.add(new Anime(++id,name, oldName, message, rating, num,imgPath)); } } catch (IOException e) { e.printStackTrace(); } finally{ cdl.countDown();//之前提到过的,使闭锁-1.这个线程每完成一次就会让闭锁-1,运行191次之后就归零了 } } }
把集合中的数据转换为TXT文件保存在工程的根目录
package bangumi; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; import java.io.ObjectOutputStream; import java.util.List; public class WriteAnimeTXT { public static void writeAnime(List<Anime> animes) { System.out.println("正在写入TXT文件..."); BufferedWriter bw = null; File file = new File("anime.txt"); try { for (Anime a : animes) { bw = new BufferedWriter(new FileWriter(file,true)); bw.write(a.toString()); bw.newLine(); bw.flush(); System.out.println(a); } System.out.println("写入完成!"); System.out.println(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally{ try { bw.close(); } catch (IOException e) { e.printStackTrace(); } } } }
把集合中的数据保存为XML文件,用的是DOM创建节点的方式,一个个创建节点并赋值,给予其层次关系
再将已经创建好的Document对象树转换为XML
package bangumi; import java.util.List; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.transform.Result; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import org.w3c.dom.Document; import org.w3c.dom.Element; public class WriteAnimeXML { public static void writeAnime(List<Anime> animes) { System.out.println("正在保存为XML文件...请稍后"); DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); try { DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); Document doc = documentBuilder.newDocument(); Element root = doc.createElement("animes");//创建根节点,名字叫animes // 遍历 for (Anime a : animes) { /* * * //排名 private int id; //名字 private String name; //原名 private * String oldName; //信息 private String message; //分数 private * double rating; //评分人数 private int number; */ Element anime = doc.createElement("anime");//创建子节点,名字叫anime Element id = doc.createElement("id");//这也是一个子节点,叫做id,等会把这个子节点塞进anime这个节点里面去 //往id的文本节点里塞遍历出来的a的id,要用文档对象的createTextNode方法,传个字符串 id.appendChild(doc.createTextNode(a.getId()+"")); Element name = doc.createElement("name"); name.appendChild(doc.createTextNode(a.getName())); Element oldName = doc.createElement("oldName"); oldName.appendChild(doc.createTextNode(a.getOldName())); Element message = doc.createElement("message"); message.appendChild(doc.createTextNode(a.getMessage())); Element rating = doc.createElement("rating"); rating.appendChild(doc.createTextNode(a.getRating()+"")); Element number = doc.createElement("number"); number.appendChild(doc.createTextNode(a.getNumber()+"")); //往anime节点里塞这些玩意 anime.appendChild(id); anime.appendChild(name); anime.appendChild(oldName); anime.appendChild(message); anime.appendChild(rating); anime.appendChild(number); root.appendChild(anime);//塞完后把anime节点塞进animes节点 } doc.appendChild(root); //document --> xml文件 TransformerFactory tf = TransformerFactory.newInstance(); Transformer t = tf.newTransformer(); //设置编码 t.setOutputProperty("encoding", "UTF-8"); //源头 DOMSource source = new DOMSource(doc); //传输的位置 Result result = new StreamResult("bangumi动画排行.xml"); t.transform(source, result); System.out.println("已经保存为XML文件"); } catch (Exception e) { } } }
把图片下载到电脑里
package bangumi; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.util.concurrent.TimeUnit; import okhttp3.OkHttpClient; import okhttp3.Request; class ImgLoader implements Runnable { private Anime anime; public ImgLoader() { super(); // TODO Auto-generated constructor stub } public ImgLoader(Anime anime) { super(); this.anime = anime; } static{ System.out.println(); System.out.println("正在下载图片...请稍后"); } @Override public void run() { // 创建存放图片的文件夹 File path = new File("bangumi动画排行榜"); if (!path.exists()) { path.mkdir(); } //创建图片的名字 String name = String.format(anime.getId()+"_"+anime.getName()+".jpg"); BufferedOutputStream out =null; try { //输出流 out = new BufferedOutputStream (new FileOutputStream(new File(path, name))); byte[] data = new OkHttpClient.Builder() .connectTimeout(60, TimeUnit.SECONDS) .readTimeout(60, TimeUnit.SECONDS) .writeTimeout(60, TimeUnit.SECONDS) .build() .newCall(new Request.Builder() .url(anime.getImagePath()).build()) .execute().body().bytes(); out.write(data); out.close(); } catch (IOException e) { e.printStackTrace(); } finally{ try { out.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
1 COMMENT