Crawler4j
Web Crawler/src/edu/smu/lyle/crawler/Controller.java
Web Crawler/src/edu/smu/lyle/crawler/Controller.java
package
edu
.
smu
.
lyle
.
crawler
;
public
class
Controller
{
public
static
void
main
(
String
[]
args
)
throws
Exception
{
String
crawlStorageFolder
=
"/data/crawl/root"
;
int
numberOfCrawlers
=
1
;
CrawlConfig
config
=
new
CrawlConfig
();
config
.
setCrawlStorageFolder
(
crawlStorageFolder
);
/*
* Instantiate the controller for this crawl.
*/
PageFetcher
pageFetcher
=
new
PageFetcher
(
config
);
RobotstxtConfig
robotstxtConfig
=
new
RobotstxtConfig
();
RobotstxtServer
robotstxtServer
=
new
RobotstxtServer
(
robotstxtConfig
,
pageFetcher
);
CrawlController
controller
=
new
CrawlController
(
config
,
pageFetcher
,
robotstxtServer
);
/*
* For each crawl, you need to add some seed urls. These are the first
* URLs that are fetched and then the crawler starts following links
* which are found in these pages
*/
controller
.
addSeed
(
"http://www.lyle.smu.edu/~fmoore/"
);
/*
* Start the crawl. This is a blocking operation, meaning that your code
* will reach the line after this only when crawling is finished.
*/
controller
.
start
(
MyCrawler
.
class
,
numberOfCrawlers
);
}
}
Web Crawler/src/edu/smu/lyle/crawler/Main.java
Web Crawler/src/edu/smu/lyle/crawler/Main.java
package
edu
.
smu
.
lyle
.
crawler
;
import
java
.
nio
.
channels
.
Pipe
;
import
java
.
util
.
regex
.
Pattern
;
public
class
Main
<
WebURL
>
{
private
final
static
Pattern
FILTERS
=
Pattern
.
compile
(
".*(\\.(css|js|gif|jpg"
+
"|png|mp3|mp3|zip|gz))$"
);
/**
* This method receives two parameters. The first parameter is the page
* in which we have discovered this new url and the second parameter is
* the new url. You should implement this function to specify whether
* the given url should be crawled or not (based on your crawling logic).
* In this example, we are instructing the crawler to ignore urls that
* have css, js, git, ... extensions and to only accept urls that start
* with "http://www.lyle.smu.edu~fmoore/". In this case, we didn't need the
* referringPage parameter to make the decision.
*/
public
boolean
shouldVisit
(
Package
referringPage
,
WebURL
url
)
{
String
href
=
((
Object
)
url
).
getURL
().
toLowerCase
();
return
!
FILTERS
.
matcher
(
href
).
matches
()
&&
href
.
startsWith
(
"http://www.lyle.smu.edu~fmoore/"
);
}
/**
* This function is called when a page is fetched and ready
* to be processed by your program.
*/
public
void
visit
(
Pipe
page
)
{
String
url
=
((
Object
)
page
)).
getWebURL
().
getURL
();
System
.
out
.
println
(
"URL: "
+
url
);
if
(
page
.
getParseData
()
instanceof
HtmlParseData
)
{
HtmlParseData
htmlParseData
=
(
HtmlParseData
)
page
.
getParseData
();
String
text
=
htmlParseData
.
getText
();
String
html
=
htmlParseData
.
getHtml
();
Set
<
WebURL
>
links
=
htmlParseData
.
getOutgoingUrls
();
System
.
out
.
println
(
"Text length: "
+
text
.
length
());
System
.
out
.
println
(
"Html length: "
+
html
.
length
());
System
.
out
.
println
(
"Number of outgoing links: "
+
links
.
size
());
}
}
}
Web Crawler/src/edu/smu/lyle/crawler/Spider.java
Web Crawler/src/edu/smu/lyle/crawler/Spider.java
package
edu
.
smu
.
lyle
.
crawler
;
import
java
.
util
.
HashSet
;
import
java
.
util
.
LinkedList
;
import
java
.
util
.
List
;
import
java
.
util
.
Set
;
public
class
Spider
{
private
static
final
int
MAX_PAGES_TO_SEARCH
=
20
;
private
Set
<
String
>
pagesVisited
=
new
HashSet
<
String
>
();
private
List
<
String
>
pagesToVisit
=
new
LinkedList
<
String
>
();
/**
* Our main launching point for the Spider's functionality. Internally it creates spider legs
* that make an HTTP request and parse the response (the web page).
*
*
@param
url
* - The starting point of the spider
*
@param
searchWord
* - The word or string that you are searching for
*/
public
void
search
(
String
url
,
String
searchWord
)
{
while
(
this
.
pagesVisited
.
size
()
<
MAX_PAGES_TO_SEARCH
)
{
String
currentUrl
;
SpiderLeg
leg
=
new
SpiderLeg
();
if
(
this
.
pagesToVisit
.
isEmpty
())
{
currentUrl
=
url
;
this
.
pagesVisited
.
add
(
url
);
}
else
{
currentUrl
=
this
.
nextUrl
();
}
leg
.
crawl
(
currentUrl
);
// Lots of stuff happening here. Look at the crawl method in
// SpiderLeg
{
System
.
out
.
println
(
String
.
format
(
"**Success** Word %s found at %s"
,
searchWord
,
currentUrl
));
break
;
}
}
System
.
out
.
println
(
"\n**Done** Visited "
+
this
.
pagesVisited
.
size
()
+
" web page(s)"
);
}
/**
* Returns the next URL to visit (in the order that they were found). We also do a check to make
* sure this method doesn't return a URL that has already been visited.
*
*
@return
*/
private
String
nextUrl
()
{
String
nextUrl
;
do
{
nextUrl
=
this
.
pagesToVisit
.
remove
(
0
);
}
while
(
this
.
pagesVisited
.
contains
(
nextUrl
));
this
.
pagesVisited
.
add
(
nextUrl
);
return
nextUrl
;
}
}
Web Crawler/src/edu/smu/lyle/crawler/SpiderLeg.java
Web Crawler/src/edu/smu/lyle/crawler/SpiderLeg.java
package
edu
.
smu
.
lyle
.
crawler
;
import
java
.
io
.
IOException
;
import
java
.
util
.
LinkedList
;
import
java
.
util
.
List
;
import
org
.
jsoup
.
Connection
;
import
org
.
jsoup
.
Jsoup
;
import
org
.
jsoup
.
nodes
.
Document
;
import
org
.
jsoup
.
nodes
.
Element
;
import
org
.
jsoup
.
select
.
Elements
;
public
class
SpiderLeg
{
// We'll use a fake USER_AGENT so the web server thinks the robot is a normal web browser.
private
static
final
String
USER_AGENT
=
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.112 Safari/535.1"
;
private
List
<
String
>
links
=
new
LinkedList
<
String
>
();
private
Document
htmlDocument
;
/**
* This performs all the work. It makes an HTTP request, checks the response, and then gathers
* up all the links on the page. Perform a searchForWord after the successful crawl
*
*
@param
url
* - The URL to visit
*
@return
whether or not the crawl was successful
*/
public
boolean
crawl
(
String
url
)
{
try
{
Connection
connection
=
Jsoup
.
connect
(
url
).
userAgent
(
USER_AGENT
);
Document
htmlDocument
=
connection
.
get
();
this
.
htmlDocument
=
htmlDocument
;
if
(
connection
.
response
().
statusCode
()
==
200
)
// 200 is the HTTP OK status code
// indicating that everything is great.
{
System
.
out
.
println
(
"\n**Visiting** Received web page at "
+
url
);
}
if
(
!
connection
.
response
().
contentType
().
contains
(
"text/html"
))
{
System
.
out
.
println
(
"**Failure** Retrieved something other than HTML"
);
return
false
;
}
Elements
linksOnPage
=
htmlDocument
.
select
(
"a[href]"
);
System
.
out
.
println
(
"Found ("
+
linksOnPage
.
size
()
+
") links"
);
for
(
Element
link
:
linksOnPage
)
{
this
.
links
.
add
(
link
.
absUrl
(
"href"
));
}
return
true
;
}
catch
(
IOException
ioe
)
{
// We were not successful in our HTTP request
return
false
;
}
}
/**
* Performs a search on the body of on the HTML document that is retrieved. This method should
* only be called after a successful crawl.
*
*
@param
searchWord
* - The word or string to look for
*
@return
whether or not the word was found
*/
public
boolean
searchForWord
(
String
searchWord
)
{
// Defensive coding. This method should only be used after a successful crawl.
if
(
this
.
htmlDocument
==
null
)
{
System
.
out
.
println
(
"ERROR! Call crawl() before performing analysis on the document"
);
return
false
;
}
System
.
out
.
println
(
"Searching for the word "
+
searchWord
+
"..."
);
String
bodyText
=
this
.
htmlDocument
.
body
().
text
();
return
bodyText
.
toLowerCase
().
contains
(
searchWord
.
toLowerCase
());
}
public
List
<
String
>
getLinks
()
{
return
this
.
links
;
}
}
Web Crawler/src/edu/smu/lyle/crawler/Stemmer.java
Web Crawler/src/edu/smu/lyle/crawler/Stemmer.java
package
edu
.
smu
.
lyle
.
crawler
;
import
java
.
io
.
*
;
/**
* Stemmer, implementing the Porter Stemming Algorithm
*
* The Stemmer class transforms a word into its root form. The input
* word can be provided a character at time (by calling add()), or at once
* by calling one of the various stem(something) methods.
*/
class
Stemmer
{
private
char
[]
b
;
private
int
i
,
/* offset into b */
i_end
,
/* offset to end of stemmed word */
j
,
k
;
private
static
final
int
INC
=
50
;
private
static
FileInputStream
fileInputStream
;
/* unit of size whereby b is increased */
public
Stemmer
()
{
b
=
new
char
[
INC
];
i
=
0
;
i_end
=
0
;
}
/**
* Add a character to the word being stemmed. When you are finished
* adding characters, you can call stem(void) to stem the word.
*/
public
void
add
(
char
ch
)
{
if
(
i
==
b
.
length
)
{
char
[]
new_b
=
new
char
[
i
+
INC
];
for
(
int
c
=
0
;
c
<
i
;
c
++
)
new_b
[
c
]
=
b
[
c
];
b
=
new_b
;
}
b
[
i
++
]
=
ch
;
}
/** Adds wLen characters to the word being stemmed contained in a portion
* of a char[] array. This is like repeated calls of add(char ch), but
* faster.
*/
public
void
add
(
char
[]
w
,
int
wLen
)
{
if
(
i
+
wLen
>=
b
.
length
)
{
char
[]
new_b
=
new
char
[
i
+
wLen
+
INC
];
for
(
int
c
=
0
;
c
<
i
;
c
++
)
new_b
[
c
]
=
b
[
c
];
b
=
new_b
;
}
for
(
int
c
=
0
;
c
<
wLen
;
c
++
)
b
[
i
++
]
=
w
[
c
];
}
/**
* After a word has been stemmed, it can be retrieved by toString(),
* or a reference to the internal buffer can be retrieved by getResultBuffer
* and getResultLength (which is generally more efficient.)
*/
public
String
toString
()
{
return
new
String
(
b
,
0
,
i_end
);
}
/**
* Returns the length of the word resulting from the stemming process.
*/
public
int
getResultLength
()
{
return
i_end
;
}
/**
* Returns a reference to a character buffer containing the results of
* the stemming process. You also need to consult getResultLength()
* to determine the length of the result.
*/
public
char
[]
getResultBuffer
()
{
return
b
;
}
/* cons(i) is true <=> b[i] is a consonant. */
private
final
boolean
cons
(
int
i
)
{
switch
(
b
[
i
])
{
case
'a'
:
case
'e'
:
case
'i'
:
case
'o'
:
case
'u'
:
return
false
;
case
'y'
:
return
(
i
==
0
)
?
true
:
!
cons
(
i
-
1
);
default
:
return
true
;
}
}
/* m() measures the number of consonant sequences between 0 and j. if c is
a consonant sequence and v a vowel sequence, and <..> indicates arbitrary
presence,
<c><v> gives 0
<c>vc<v> gives 1
<c>vcvc<v> gives 2
<c>vcvcvc<v> gives 3
....
*/
private
final
int
m
()
{
int
n
=
0
;
int
i
=
0
;
while
(
true
)
{
if
(
i
>
j
)
return
n
;
if
(
!
cons
(
i
))
break
;
i
++
;
}
i
++
;
while
(
true
)
{
while
(
true
)
{
if
(
i
>
j
)
return
n
;
if
(
cons
(
i
))
break
;
i
++
;
}
i
++
;
n
++
;
while
(
true
)
{
if
(
i
>
j
)
return
n
;
if
(
!
cons
(
i
))
break
;
i
++
;
}
i
++
;
}
}
/* vowelinstem() is true <=> 0,...j contains a vowel */
private
final
boolean
vowelinstem
()
{
int
i
;
for
(
i
=
0
;
i
<=
j
;
i
++
)
if
(
!
cons
(
i
))
return
true
;
return
false
;
}
/* doublec(j) is true <=> j,(j-1) contain a double consonant. */
private
final
boolean
doublec
(
int
j
)
{
if
(
j
<
1
)
return
false
;
if
(
b
[
j
]
!=
b
[
j
-
1
])
return
false
;
return
cons
(
j
);
}
/* cvc(i) is true <=> i-2,i-1,i has the form consonant - vowel - consonant
and also if the second c is not w,x or y. this is used when trying to
restore an e at the end of a short word. e.g.
cav(e), lov(e), hop(e), crim(e), but
snow, box, tray.
*/
private
final
boolean
cvc
(
int
i
)
{
if
(
i
<
2
||
!
cons
(
i
)
||
cons
(
i
-
1
)
||
!
cons
(
i
-
2
))
return
false
;
{
int
ch
=
b
[
i
];
if
(
ch
==
'w'
||
ch
==
'x'
||
ch
==
'y'
)
return
false
;
}
return
true
;
}
private
final
boolean
ends
(
String
s
)
{
int
l
=
s
.
length
();
int
o
=
k
-
l
+
1
;
if
(
o
<
0
)
return
false
;
for
(
int
i
=
0
;
i
<
l
;
i
++
)
if
(
b
[
o
+
i
]
!=
s
.
charAt
(
i
))
return
false
;
j
=
k
-
l
;
return
true
;
}
/* setto(s) sets (j+1),...k to the characters in the string s, readjusting
k. */
private
final
void
setto
(
String
s
)
{
int
l
=
s
.
length
();
int
o
=
j
+
1
;
for
(
int
i
=
0
;
i
<
l
;
i
++
)
b
[
o
+
i
]
=
s
.
charAt
(
i
);
k
=
j
+
l
;
}
/* r(s) is used further down. */
private
final
void
r
(
String
s
)
{
if
(
m
()
>
0
)
setto
(
s
);
}
/* step1() gets rid of plurals and -ed or -ing. e.g.
caresses -> caress
ponies -> poni
ties -> ti
caress -> caress
cats -> cat
feed -> feed
agreed -> agree
disabled -> disable
matting -> mat
mating -> mate
meeting -> meet
milling -> mill
messing -> mess
meetings -> meet
*/
private
final
void
step1
()
{
if
(
b
[
k
]
==
's'
)
{
if
(
ends
(
"sses"
))
k
-=
2
;
else
if
(
ends
(
"ies"
))
setto
(
"i"
);
else
if
(
b
[
k
-
1
]
!=
's'
)
k
--
;
}
if
(
ends
(
"eed"
))
{
if
(
m
()
>
0
)
k
--
;
}
else
if
((
ends
(
"ed"
)
||
ends
(
"ing"
))
&&
vowelinstem
())
{
k
=
j
;
if
(
ends
(
"at"
))
setto
(
"ate"
);
else
if
(
ends
(
"bl"
))
setto
(
"ble"
);
else
if
(
ends
(
"iz"
))
setto
(
"ize"
);
else
if
(
doublec
(
k
))
{
k
--
;
{
int
ch
=
b
[
k
];
if
(
ch
==
'l'
||
ch
==
's'
||
ch
==
'z'
)
k
++
;
}
}
else
if
(
m
()
==
1
&&
cvc
(
k
))
setto
(
"e"
);
}
}
/* step2() turns terminal y to i when there is another vowel in the stem. */
private
final
void
step2
()
{
if
(
ends
(
"y"
)
&&
vowelinstem
())
b
[
k
]
=
'i'
;
}
/* step3() maps double suffices to single ones. so -ization ( = -ize plus
-ation) maps to -ize etc. note that the string before the suffix must give
m() > 0. */
private
final
void
step3
()
{
if
(
k
==
0
)
return
;
/* For Bug 1 */
switch
(
b
[
k
-
1
])
{
case
'a'
:
if
(
ends
(
"ational"
))
{
r
(
"ate"
);
break
;
}
if
(
ends
(
"tional"
))
{
r
(
"tion"
);
break
;
}
break
;
case
'c'
:
if
(
ends
(
"enci"
))
{
r
(
"ence"
);
break
;
}
if
(
ends
(
"anci"
))
{
r
(
"ance"
);
break
;
}
break
;
case
'e'
:
if
(
ends
(
"izer"
))
{
r
(
"ize"
);
break
;
}
break
;
case
'l'
:
if
(
ends
(
"bli"
))
{
r
(
"ble"
);
break
;
}
if
(
ends
(
"alli"
))
{
r
(
"al"
);
break
;
}
if
(
ends
(
"entli"
))
{
r
(
"ent"
);
break
;
}
if
(
ends
(
"eli"
))
{
r
(
"e"
);
break
;
}
if
(
ends
(
"ousli"
))
{
r
(
"ous"
);
break
;
}
break
;
case
'o'
:
if
(
ends
(
"ization"
))
{
r
(
"ize"
);
break
;
}
if
(
ends
(
"ation"
))
{
r
(
"ate"
);
break
;
}
if
(
ends
(
"ator"
))
{
r
(
"ate"
);
break
;
}
break
;
case
's'
:
if
(
ends
(
"alism"
))
{
r
(
"al"
);
break
;
}
if
(
ends
(
"iveness"
))
{
r
(
"ive"
);
break
;
}
if
(
ends
(
"fulness"
))
{
r
(
"ful"
);
break
;
}
if
(
ends
(
"ousness"
))
{
r
(
"ous"
);
break
;
}
break
;
case
't'
:
if
(
ends
(
"aliti"
))
{
r
(
"al"
);
break
;
}
if
(
ends
(
"iviti"
))
{
r
(
"ive"
);
break
;
}
if
(
ends
(
"biliti"
))
{
r
(
"ble"
);
break
;
}
break
;
case
'g'
:
if
(
ends
(
"logi"
))
{
r
(
"log"
);
break
;
}
}
}
/* step4() deals with -ic-, -full, -ness etc. similar strategy to step3. */
private
final
void
step4
()
{
switch
(
b
[
k
])
{
case
'e'
:
if
(
ends
(
"icate"
))
{
r
(
"ic"
);
break
;
}
if
(
ends
(
"ative"
))
{
r
(
""
);
break
;
}
if
(
ends
(
"alize"
))
{
r
(
"al"
);
break
;
}
break
;
case
'i'
:
if
(
ends
(
"iciti"
))
{
r
(
"ic"
);
break
;
}
break
;
case
'l'
:
if
(
ends
(
"ical"
))
{
r
(
"ic"
);
break
;
}
if
(
ends
(
"ful"
))
{
r
(
""
);
break
;
}
break
;
case
's'
:
if
(
ends
(
"ness"
))
{
r
(
""
);
break
;
}
break
;
}
}
/* step5() takes off -ant, -ence etc., in context <c>vcvc<v>. */
private
final
void
step5
()
{
if
(
k
==
0
)
return
;
/* for Bug 1 */
switch
(
b
[
k
-
1
])
{
case
'a'
:
if
(
ends
(
"al"
))
break
;
return
;
case
'c'
:
if
(
ends
(
"ance"
))
break
;
if
(
ends
(
"ence"
))
break
;
return
;
case
'e'
:
if
(
ends
(
"er"
))
break
;
return
;
case
'i'
:
if
(
ends
(
"ic"
))
break
;
return
;
case
'l'
:
if
(
ends
(
"able"
))
break
;
if
(
ends
(
"ible"
))
break
;
return
;
case
'n'
:
if
(
ends
(
"ant"
))
break
;
if
(
ends
(
"ement"
))
break
;
if
(
ends
(
"ment"
))
break
;
/* element etc. not stripped before the m */
if
(
ends
(
"ent"
))
break
;
return
;
case
'o'
:
if
(
ends
(
"ion"
)
&&
j
>=
0
&&
(
b
[
j
]
==
's'
||
b
[
j
]
==
't'
))
break
;