获取网页html
刚开始做的时候,在网上搜了一下资料。然后找到了一个获取网页最简单的dome,如下。
public static String getHtml(String urlstring) throws IOException {
//得到地址
URL url = new URL(urlstring);
//建立连接
URLConnection conn = url.openConnection();
//获得数据
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(conn.getInputStream(), "gbk"));
StringBuilder html = new StringBuilder();
String line;
//正则表达式匹配
// String reg = "\\w+@\\w+";
// Pattern pattern = Pattern.compile(reg);
while ((line = bufferedReader.readLine()) != null) {
html.append(line);
}
return html.toString();
}
于是就按照这个开始编写我自己的爬虫。
如何爬url
获取电影详情URL
我发现电影天堂的网址是很有规律的。比如它的最新电影列表的url是:”http://www.ygdy8.net/html/gndy/dyzz/list_23_NUM.html“,NUM是1到258
所以我就没有用爬虫爬url,直接就循环NUM来获取它页面下电影详情的url。因为这个url数量不多,我就没有考虑用多线程。直接其爬完,然后序列化到文件中保存起来。
public class SpiderForMovies extends Spider{
private static Vector<String> urlList = new Vector<String>();
@Override
public void execute(String url) throws IOException {
//根据Url地址获取网页内容
String html = HttpUtils.getHtml(url);
if (html == null){
throw new RuntimeException("无法获取url网址内容");
}
//对网页内容进行分析和提取
Document doc = Jsoup.parse(html);
Elements tables = doc.select("table.tbspan");//class等于tbspan的table标签
Elements links = tables.select("a[href]"); //带有href属性的a元素
for (Element link : links) {
String linkHref = link.attr("href");
// String linkHref = homepage + Href;
//加入到Url队列
urlList.add(linkHref);
}
}
public Vector<String> getUrllist() {
return urlList;
}
}
public static void getMovieUrl() throws IOException, InstantiationException, IllegalAccessException {
Integer i = 1;
while(i <= 258) {
String url = url1.replace("NUM", i.toString());
SpiderForMovies spiderForMovies = new SpiderForMovies();
spiderForMovies.execute(url);
Vector<String> temp = spiderForMovies.getUrllist();
//合并两个hashset
urlList.addAll(temp);
i++;
System.out.println(i);
}
Util.OutPut("E:\\ling\\url.txt",urlList);
}
获取电影下载地址
上面总共爬到了八十万的电影详情地址。那么肯定要使用多线程来爬取每个地址下的电影下载地址。在编写多线程获取的时候碰到了很多问题。
现在在爬下载地址了,又发现了很多问题。太大了不能一下子保存,看来还是要放到数据库中。
首先来看代码吧
//获取电影详情
public static void getMovieParticular() throws InterruptedException {
int N = 8;
CountDownLatch doneSignal = new CountDownLatch(N);
CountDownLatch startSignal = new CountDownLatch(1);// 开始执行信号
urlList = Util.InPut("E:\\ling\\url.txt");
SpiderThread spiderThread = new SpiderThread(urlList,doneSignal,startSignal);
for (int i = 1; i <= N; i++) {
new Thread(spiderThread).start();// 线程启动了
}
System.out.println("begin------------");
startSignal.countDown();// 开始执行啦
doneSignal.await();// 等待所有的线程执行完毕
movieList = spiderThread.getMovies();
Util.OutPutMovie("E:\\ling\\Movie.txt", movieList);
// System.out.println(movieList);
System.out.println("Ok");
}
/**
* 获取电影详情爬虫
* Created by ling on 2015/5/25.
*/
public class SpiderForMovParticular extends Spider {
private Movie movie;
@Override
public void execute(String url) throws IOException {
movie = new Movie();
//根据Url地址获取网页内容
String homepage = "http://www.ygdy8.net";
String html = HttpUtils.getHtml(homepage + url);
if (html == null){
throw new RuntimeException("无法获取url网址内容");
}
//对网页内容进行分析和提取
Document doc = Jsoup.parse(html);
//提取标题
Element link = doc.select("div.title_all").last();//class等于title_all的div标签
String title = link.text();
//提取链接
Elements elements = doc.select("div#Zoom");
Element element = elements.select("a[href]").first();
String href = element.attr("href");
//加入到Movie中
movie.setTitle(title);
movie.setDownlodeUrl(href);
}
public Movie getMovie() {
return movie;
}
}
/**
* 爬虫线程
* Created by ling on 2015/5/27.
*/
public class SpiderThread implements Runnable{
//线程开始结束的信号
private final CountDownLatch doneSignal;
private final CountDownLatch startSignal;
private static Vector<String> urlList;
private static Set<Movie> movieSet = new HashSet<Movie>();
private static int i = 0;
public SpiderThread(Vector<String> urlList, CountDownLatch doneSignal,
CountDownLatch startSignal) {
SpiderThread.urlList = urlList;
this.doneSignal = doneSignal;
this.startSignal = startSignal;
}
@Override
public void run() {
try {
startSignal.await(); // 等待开始执行信号的发布
SpiderForMovParticular spider = new SpiderForMovParticular();
while (!urlList.isEmpty()) {
//得到Url
String tmp = getAurl();
if (tmp != null) {
try {
System.out.println(i + ":" + tmp);
i++;
spider.execute(tmp);
movieSet.add(spider.getMovie());
// System.out.println(movieSet);
} catch (IOException e) {
e.printStackTrace();
}
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
doneSignal.countDown();
}
// SpiderForMovParticular spider = new SpiderForMovParticular();
// while (!urlList.isEmpty()) {
// //得到Url
// String tmp = getAurl();
//
// if (tmp != null) {
// try {
// spider.execute(tmp);
// movieSet.add(spider.getMovie());
// } catch (IOException e) {
// e.printStackTrace();
// }
// }
//
// }
}
public synchronized String getAurl() {
if (urlList.isEmpty()){
return null;
}
String tmpAUrl;
tmpAUrl = urlList.get(0);
urlList.remove(0);
return tmpAUrl;
}
public Set<Movie> getMovies() {
return movieSet;
}
}
首先是几个线程一起来爬存在UrlList中的地址。一开始就是用实现Runnable接口。
在run方法中:取出一个Url地址,执行爬虫的execute方法获取电影名称和下载地址,存在Movie对象中,再将此对象,加入MovieSet中。然后将MovieSet序列化到文件中。接下来可以将其放到数据库中。
过程就是这么简单,但是当用多线程方式将其实现时,碰到了如下几个问题:
1、从urlList中取url的时,同一个url多次被取出。
因为当多个线程同时去访问urlList取数据,就可能会取出相同的url。
所以应该在取出一条数据后将其删除,并保证其他线程不会同时取到别的线程去到的url。那么就要将取url这步同步。
public synchronized String getAurl() {
if (urlList.isEmpty()){
return null;
}
String tmpAUrl;
tmpAUrl = urlList.get(0);
urlList.remove(0);
return tmpAUrl;
}
2、多个线程如何在所有线程结束后将整个movieSet序列化
一开始我使用了 thread.join() 命令 ,看起来好像也可以使几个线程运行完以后再执行下面的步骤。但是这样做不规范。
后来我上网查了资料,使用了开始信号和结束信号作为线程开始和结束的标志。
//线程开始结束的信号
private final CountDownLatch doneSignal;
private final CountDownLatch startSignal;
public SpiderThread(Vector<String> urlList, CountDownLatch doneSignal,
CountDownLatch startSignal) {
SpiderThread.urlList = urlList;
this.doneSignal = doneSignal;
this.startSignal = startSignal;
}
@Override
public void run() {
try {
startSignal.await(); // 等待开始执行信号的发布
SpiderForMovParticular spider = new SpiderForMovParticular();
while (!urlList.isEmpty()) {
//得到Url
String tmp = getAurl();
if (tmp != null) {
try {
System.out.println(i + ":" + tmp);
i++;
spider.execute(tmp);
movieSet.add(spider.getMovie());
synchronized (this) {
if (movieSet.size() >= (10000)) {
//储存movieSet
saveSet();
}
}
// System.out.println(movieSet);
} catch (IOException e) {
e.printStackTrace();
}
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
doneSignal.countDown();
}
//获取电影详情
public static void getMovieParticular() throws InterruptedException {
int N = 8;
CountDownLatch doneSignal = new CountDownLatch(N);
CountDownLatch startSignal = new CountDownLatch(1);// 开始执行信号
urlList = Util.InPut("E:\\ling\\url.txt");
// urlList = Util.InPut("G:\\test.txt");
SpiderThread spiderThread = new SpiderThread(urlList,doneSignal,startSignal);
for (int i = 1; i <= N; i++) {
new Thread(spiderThread).start();// 线程启动了
}
System.out.println("begin------------");
startSignal.countDown();// 开始执行啦
doneSignal.await();// 等待所有的线程执行完毕
//只有所有线程执行完毕后才执行接下来的几部
movieList = spiderThread.getMovies();
Util.OutPutMovie("E:\\ling\\Movie\\MovieLast.txt", movieList);
// System.out.println(movieList);
System.out.println("Ok");
}
3、在一开始每次在movieSet中开几个线程,才有几个数据,前面的数据会被覆盖掉。找了很多,最后才发现原来是在SpiderForMovParticular类中,我将Movie 直接实例化了。就像:
private Movie movie = new Movie();
这样就不能每次都将不同的movie加入到movieSet中。
应该只创建一个引用,然后在方法中再实例化。
如:
public class SpiderForMovParticular extends Spider {
private Movie movie;
@Override
public void execute(String url) throws IOException {
movie = new Movie();
//根据Url地址获取网页内容
String homepage = "http://www.ygdy8.net";
String html = HttpUtils.getHtml(homepage + url);
if (html == null){
throw new RuntimeException("无法获取url网址内容");
}
//对网页内容进行分析和提取
Document doc = Jsoup.parse(html);
//提取标题
String title = null;
Element link = doc.select("div.title_all").last();//class等于title_all的div标签
if (link != null){
title = link.text();
}
//提取链接
String href = null;
Elements elements = doc.select("div#Zoom");
Element element = elements.select("a[href]").first();
if (element != null) {
href = element.attr("href");
}
//加入到Movie中
movie.setTitle(title);
movie.setDownlodeUrl(href);
}
public Movie getMovie() {
return movie;
}
}
最后
总共爬到了825694条电影地址,现在还在爬电影的下载地址。应该很快就能爬完。