第一步获取网页源代码:
class:
package webcrawler.common;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
/**
* 根据URL获取页面文件源代码
* @author yangkun
*
*/
public class HTMLPage {
static int RESPONSE_BYTE_SIZE=10240;//内存缓存大小
StringBuffer page;//保存源代码字符串
InputStream is;//获取URL返回的输入流
URL url;//连接地址
/**
* 构造函数
*
* */
public HTMLPage(){
page=new StringBuffer();
}
/**
* 通过给定一个url来获取到网页的源代码
* 由于网洛的问题,如将输入流的内容一次性保存到本地内存,可能会丢失部分信息
* 所以循环读取一定数量字节到本地内存
* @param url 网页链接地址 如:http:www.163.com
* @throws IOException
* @return String 网页的源代码字符串
* */
public String getHTMLpage(String url ,String charset) throws IOException{
this.url=new URL(url);
HttpURLConnection huc=(HttpURLConnection) this.url.openConnection();//获取连接
is=huc.getInputStream();//连接响应返回的输入流
byte[] bb;
int mark=1;//记录输入流的结束状态
while(mark>0){
bb = new byte[RESPONSE_BYTE_SIZE];
mark=is.read(bb);
if(mark<0){
break;
}
page.append(new String(bb,0,mark,charset));//将从输入流读取的字节变为字符串保存
}
is.close();
huc.disconnect();
return page.toString();
}
/**
* 通过给定一个url来获取到网页的源代码的输入流
* 由于网络方面的原因,容易导致错误,不建议使用
* @throws IOException
* @param url 网页链接地址 如:http:www.163.com
* return String 网页的源代码输入流
* */
public InputStream getInputStream(String url) throws IOException{
this.url=new URL(url);
HttpURLConnection huc=(HttpURLConnection) this.url.openConnection();//获取连接
return huc.getInputStream();//连接响应返回的输入流
}
}
第二步,解析html文件,dom和sax,我使用dom方式,没使用第三方工具
class:
/**
*
*/
package webcrawler.Service;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.List;
import java.util.Vector;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
/**
* @author yangkun
*
*/
public class PraseXMLHandler {
public PraseXMLHandler(){
}
/**
* 解析Dom获取标签连接队列
* @author yangkun
* @throws ParserConfigurationException
* @throws IOException
* @throws SAXException
* @param keycontext 页面源代码
* @param tagname 根节点标签名
* @param nodename 子节点标签名
* @param nodeattr 子节点标签属性数组
* @return list
* */
public List<String> parsersXmlForLink(String keycontext,String tagname,String nodename,String nodeattr) throws ParserConfigurationException, SAXException, IOException {
List<String> list = new Vector<String>();
//实例化一个文档构建器工厂
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
//通过文档构建器工厂获取一个文档构建器
DocumentBuilder db = dbf.newDocumentBuilder();
ByteArrayInputStream bis=new ByteArrayInputStream(
keycontext.getBytes("utf-8"));
//通过文档通过文档构建器构建一个文档实例
Document doc = db.parse(bis);
//获取所有名字为 的节点
NodeList nl1 = doc.getElementsByTagName(tagname);
int size1 = nl1.getLength();
for (int i = 0; i < size1; i++) {
Node n = nl1.item(i);
//获取 n 节点下所有的子节点。此处值得注意,在DOM解析时会将所有回车都视为 n 节点的子节点。
NodeList nl2 = n.getChildNodes();
//因为上面的原因,在此例中第一个 n 节点有 2 个子节点,而第二个 n 节点则有 5 个子节点(因为多了3个回车)。
int size2 = nl2.getLength();
for (int j = 0; j < size2; j++) {
Node n2 = nl2.item(j);
//还是因为上面的原因,故此要处判断当 n2 节点有子节点的时才输出。
if (n2.hasChildNodes()) {
if(n2.getNodeName().equals(nodename)){
list.add(n2.getAttributes().getNamedItem(nodeattr).getTextContent());
}
}
}
}
return list;
}
/**
* 解析Dom获取下一页a连接队列
* @author yangkun
* @throws ParserConfigurationException
* @throws IOException
* @throws SAXException
* @param keycontext 页面源代码
* @return String 字符串
* */
public String parsersXmlForward(String keycontext) throws ParserConfigurationException, SAXException, IOException {
String url="";
//实例化一个文档构建器工厂
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
//通过文档构建器工厂获取一个文档构建器
DocumentBuilder db = dbf.newDocumentBuilder();
ByteArrayInputStream bis=new ByteArrayInputStream(
keycontext.getBytes());
//通过文档通过文档构建器构建一个文档实例
Document doc = db.parse(bis);
//获取所有名字为 “person” 的节点
NodeList nl1 = doc.getElementsByTagName("div");
int size1 = nl1.getLength();
for (int i = 0; i < size1; i++) {
Node n = nl1.item(i);
//获取 n 节点下所有的子节点。此处值得注意,在DOM解析时会将所有回车都视为 n 节点的子节点。
NodeList nl2 = n.getChildNodes();
//因为上面的原因,在此例中第一个 n 节点有 2 个子节点,而第二个 n 节点则有 5 个子节点(因为多了3个回车)。
int size2 = nl2.getLength();
for (int j = 0; j < size2; j++) {
Node n2 = nl2.item(j);
//还是因为上面的原因,故此要处判断当 n2 节点有子节点的时才输出。
if (n2.hasChildNodes()) {
if(n2.getTextContent().equals("下一页")){
url=n2.getAttributes().getNamedItem("href").getTextContent();
}
}
}
}
return url;
}
/**
* 解析Dom获取网页信息
* @author yangkun
* @throws ParserConfigurationException
* @throws IOException
* @throws SAXException
* @param keycontext 页面源代码
* @param tagname 根节点标签名
* @param nodename 子节点标签名
* @return String 字符串
* */
public String parsersXmlForContent(String keycontext,String tagname,String nodename) throws ParserConfigurationException, SAXException, IOException {
//实例化一个文档构建器工厂
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
StringBuffer result=new StringBuffer();
//通过文档构建器工厂获取一个文档构建器
DocumentBuilder db = dbf.newDocumentBuilder();
ByteArrayInputStream bis=new ByteArrayInputStream(
keycontext.replace('&', '$').getBytes());
//通过文档通过文档构建器构建一个文档实例
Document doc = db.parse(bis);
//获取所有名字为 tagname 的节点
NodeList nl1 = doc.getElementsByTagName(tagname);
int size1 = nl1.getLength();
for (int i = 0; i < size1; i++) {
Node n = nl1.item(i);
//获取 n 节点下所有的子节点。此处值得注意,在DOM解析时会将所有回车都视为 n 节点的子节点。
NodeList nl2 = n.getChildNodes();
//因为上面的原因,在此例中第一个 n 节点有 2 个子节点,而第二个 n 节点则有 5 个子节点(因为多了3个回车)。
int size2 = nl2.getLength();
for (int j = 0; j < size2; j++) {
Node n2 = nl2.item(j);
//还是因为上面的原因,故此要处判断当 n2 节点有子节点的时才输出。
if (n2.hasChildNodes()) {
if(n2.getNodeName().equals(nodename)){
result.append(n2.getTextContent());
}
}
}
}
return result.toString().replace('$', '&');
}
/**
* 解析Dom,根据标签name获取网页信息
* @author yangkun
* @throws ParserConfigurationException
* @throws IOException
* @throws SAXException
* @param keycontext 页面源代码
* @param tagname 根节点标签名
* @return String 字符串
* */
public String parsersXmlForTag(String keycontext,String tagname) throws ParserConfigurationException, SAXException, IOException {
//实例化一个文档构建器工厂
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
StringBuffer result=new StringBuffer();
//通过文档构建器工厂获取一个文档构建器
DocumentBuilder db = dbf.newDocumentBuilder();
ByteArrayInputStream bis=new ByteArrayInputStream(
keycontext.replace('&', '$').getBytes());
//通过文档通过文档构建器构建一个文档实例
Document doc = db.parse(bis);
//获取所有名字为 tagname 的节点
NodeList nl1 = doc.getElementsByTagName(tagname);
if(tagname.equals("div")){
result.append(nl1.item(0).getTextContent());
}else{
for(int index=0;index<nl1.getLength();index++){
result.append(nl1.item(index).getTextContent());
}
}
return result.toString().replace('$', '&');
}
/**
* 解析Dom,根据标签name获取网页信息
* @author yangkun
* @throws ParserConfigurationException
* @throws IOException
* @throws SAXException
* @param keycontext 页面源代码
* @param tagname 根节点标签名
* @param tagAttr 标签属性
* @return String 字符串
* */
public String parsersXmlForTagAttr(String keycontext,String tagname,String tagAttr) throws ParserConfigurationException, SAXException, IOException {
//实例化一个文档构建器工厂
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
StringBuffer result=new StringBuffer();
//通过文档构建器工厂获取一个文档构建器
DocumentBuilder db = dbf.newDocumentBuilder();
ByteArrayInputStream bis=new ByteArrayInputStream(
keycontext.replace('&', '$').getBytes());
//通过文档通过文档构建器构建一个文档实例
Document doc = db.parse(bis);
//获取所有名字为 tagname 的节点
NodeList nl1 = doc.getElementsByTagName(tagname);
if(tagname.equals("div")){
result.append(nl1.item(0).getTextContent());
}else{
for(int index=0;index<nl1.getLength();index++){
result.append(nl1.item(index).getTextContent());
}
}
return result.toString().replace('$', '&');
}
}
第三步,获取网页相关信息,主程序入口
/**
*
*/
package com.gangyi.webcrawler.common;
import java.io.IOException;
import java.sql.SQLException;
import java.util.List;
import java.util.Vector;
import javax.xml.parsers.ParserConfigurationException;
import org.xml.sax.SAXException;
import com.gangyi.webcrawler.Service.PraseXMLHandler;
import com.gangyi.webcrawler.dao.ConnectMySQL;
import com.gangyi.webcrawler.vo.Message;
/**
* 网络爬虫的程序入口
* @author yangkun
*
*/
public class WebCrawler {
/**
* 根据URL获得需要的信息
* @param url
* @throws IOException
* @throws SAXException
* @throws ParserConfigurationException
* */
public Message getMessage(String url) throws IOException, ParserConfigurationException, SAXException{
HTMLPage hp=new HTMLPage();
PraseXMLHandler pxh=new PraseXMLHandler();
TagString ts=new TagString();
Message msg=new Message();
String filename,keycontext;
filename=hp.getHTMLpage(url,"gb2312");
//获取路径
keycontext=filename.substring(filename.indexOf("<div id=\"naver\">"));
keycontext=ts.getEndTagString(keycontext,"<div","</div>");
msg.setUrl(pxh.parsersXmlForTag(keycontext, "div")
.replace("\t", "").replace("\n", ""));
//获取时间
keycontext=filename.substring(filename.indexOf("<div class=\"info\">"));
keycontext=ts.getEndTagString(keycontext,"<div","</div>");
msg.setDate(pxh.parsersXmlForTag(keycontext, "div").substring(0, 16));
//获取标题
keycontext=filename.substring(filename.indexOf("<div id=\"articleContent\">"));
keycontext=ts.getEndTagString(keycontext,"<div","</div>");
msg.setTitle(pxh.parsersXmlForTag(keycontext, "h1"));
//获取内容
keycontext=filename.substring(filename.indexOf("<div class=\"text\" id=\"text\">"));
keycontext=ts.getEndTagString(keycontext,"<div","</div>");
msg.setContent(pxh.parsersXmlForTag(keycontext, "div")
.replace("\t", "").replace("\n", "").replace(" ", ""));
System.out.println(msg.getUrl()+" "+msg.getTitle()+" "+msg.getDate()+" "+msg.getContent().substring(0, 100)+"....");
return msg;
}
/**
* 根据URL获得url list信息
* @param url
* @throws IOException
* @throws SAXException
* @throws ParserConfigurationException
* */
public List<String> geturllist(String url) throws IOException, ParserConfigurationException, SAXException{
HTMLPage hp=new HTMLPage();
PraseXMLHandler pxh=new PraseXMLHandler();
TagString ts=new TagString();
List<String> list = new Vector<String>();
String filename,keycontext;
filename=hp.getHTMLpage(url,"gb2312");
keycontext=filename.substring(filename.indexOf("<ul class=\"nlist\">"));
keycontext=ts.getEndTagString(keycontext,"<ul","</ul>");
list=pxh.parsersXmlForLink(keycontext,"li", "a","href");
return list;
}
/**
* 根据URL获得url list信息
* @param url
* @throws IOException
* @throws SAXException
* @throws ParserConfigurationException
* */
public String getnextlist(String url) throws IOException, ParserConfigurationException, SAXException{
HTMLPage hp=new HTMLPage();
PraseXMLHandler pxh=new PraseXMLHandler();
TagString ts=new TagString();
String list ;
String filename,keycontext;
filename=hp.getHTMLpage(url,"gb2312");
keycontext=filename.substring(filename.indexOf("div class=\"page\">"));
keycontext=ts.getEndTagString(keycontext,"<div","</div>");
list=pxh.parsersXmlForward(keycontext);
return list;
}
/**
* 保存数据
* @throws SQLException
* @throws ClassNotFoundException
* @throws SAXException
* @throws ParserConfigurationException
* @throws IOException
* */
public void saveData(String url) throws ClassNotFoundException, SQLException, IOException, ParserConfigurationException, SAXException{
ConnectMySQL con=new ConnectMySQL();
con.connect();
Message msg=this.getMessage(url);
con.insert(msg);
con.closeConnection();
}
/**
* 开始运行爬虫
* @throws SQLException
* @throws ClassNotFoundException
* @throws SAXException
* @throws ParserConfigurationException
* @throws IOException
* */
public void start(String url) throws ClassNotFoundException, SQLException, IOException, ParserConfigurationException, SAXException{
List<String> list=this.geturllist(url);
for(int ind=0;ind<list.size();ind++){
try{
this.saveData(list.get(ind));
}catch(Exception e){
}
}
if(!this.getnextlist(url).equals("")){
this.start(this.getnextlist(url));
}
}
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
try{
String url="http://www.xxxxx.com";
WebCrawler wc=new WebCrawler();
wc.start(url);
}catch(Exception e){
e.printStackTrace();
}
}
}
补充说明:
我的设计是,获取源代码后,切割需要获取信息的字符串,在去dom解析,因为dom对于大文件的解析效率太低了。
这样就会有一些解决的问题,比喻网页中有很多的div标签镶嵌,那么如何判断指定id或那么的div的结束标签
下面是我的代码:
/**
*
*/
package webcrawler.common;
/**
* 页面源代码的标签字符串
* @author yangkun
*
*/
public class TagString {
/**
* 根据给定的字符串来判断div标签到达结尾标签,返回的到的字符串
* 如果div里有多个div,这将源代码字符串以""拆分为字符串数组。
* 在遍历每个数组,再次将其以"”
* @param endTag 结束标签 ,用于第一次拆分为字符串数组
* @return str 需要剪切字符串参数
* */
public String getEndTagString(String str,String startTag,String endTag){
int addindex=endTag.length();
String[] strs=str.split(endTag);
String tempstr=strs[0];
String[] tempstrs=tempstr.split(startTag);
int lastindex=tempstr.length()+addindex;//这个参数是记录标签到达结尾的字符串索引
int size=tempstrs.length-2;
int mark=0;
if(size!=0){
for (int index=1;index2){
size=size+mark-2;
}
}
lastindex+=tempstr.length()+addindex;
if(size==0){
break;
}
}
}
return str.substring(0,lastindex);
}
}