获取网页html
刚开始做的时候,在网上搜了一下资料。然后找到了一个获取网页最简单的dome,如下。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23public 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数量不多,我就没有考虑用多线程。直接其爬完,然后序列化到文件中保存起来。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53public class SpiderForMovies extends Spider{
private static Vector<String> urlList = new Vector<String>();
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);
}
##获取电影下载地址
上面总共爬到了八十万的电影详情地址。那么肯定要使用多线程来爬取每个地址下的电影下载地址。在编写多线程获取的时候碰到了很多问题。
现在在爬下载地址了,又发现了很多问题。太大了不能一下子保存,看来还是要放到数据库中。
首先来看代码吧1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156//获取电影详情
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;
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;
}
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这步同步。
1 | public synchronized String getAurl() { |
2、多个线程如何在所有线程结束后将整个movieSet序列化
一开始我使用了 thread.join() 命令 ,看起来好像也可以使几个线程运行完以后再执行下面的步骤。但是这样做不规范。
后来我上网查了资料,使用了开始信号和结束信号作为线程开始和结束的标志。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71 //线程开始结束的信号
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;
}
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 直接实例化了。就像:
1 private Movie movie = new Movie();
这样就不能每次都将不同的movie加入到movieSet中。
应该只创建一个引用,然后在方法中再实例化。
如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43 public class SpiderForMovParticular extends Spider {
private Movie movie;
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条电影地址,现在还在爬电影的下载地址。应该很快就能爬完。