爬虫程序需要掌握的几个知识:
1、首先需要使用XPath对网页进行下载和解析,需要导入htmlcleaner.jar包,去官网下载即可。
2、需要了解网页元素定位方式,当然谷歌浏览器自带开发者工具,直接选中需要定位的部分,右键选中检查或者Ctrl+Shift+I即可进行定位,定位好的html代码右键选中copy-->Copy XPath,即可生成xpath语法。
目前遇到的问题:
1、能够爬取定位好的某一个元素,但是换成/td之后的所有子元素之后,爬取的对象会拼接成一条字符串,将每一个元素分离出来需要写额外的代码,希望可以通过Object[]对象数组在爬取时就进行分离,但是编译无法通过,报越界问题,原因是一个元素也没存入。
2、手语网中的词汇描述和词汇是放在一个td之中的,通过换行符<br>将其分割,目前无法分别提取出词汇和词汇描述,只能将整个<td>之中的内容提取出来。
3、手语网中有很多分类,每个分类之中又有很多分页,所有需要全部存入数据库,需要编写更换url的代码。
解决办法:
1、通过对Xpath的学习和理解,了解了Xpath的语法,之前直接定位检查某一小块的html代码,然后复制xpth语法相应的只能爬取该小块的内容,所以我们需要自己写Xpath语法。
通过逐个定位手语网的结构,了解到网页是由两个大的table构成,第一个存分类,第二个存这类的词汇,而第二个table中又有很多table,用于存储单条信息,诸如词汇描述图片。分析完成之后,操作方法就是分两步爬取,第一步爬取第二个table中的所有table条目,存储类型为Object[]:
Object[] obje = tn.evaluateXPath("//*[@id=\"main_content\"]/table/tbody/tr/td[2]/table");
第二步对于获取到的每一个table对象进行解析,即拆出词汇、描述、图片网址;
if(obje!=null&&obje.length>0) {
for(Object obj:obje) {
TagNode tntr = (TagNode) obj;
String vocabulary=tntr.evaluateXPath("//tbody/tr/td/table/tbody/tr/td[1]/a/strong/text()")[0].toString();
String description=tntr.evaluateXPath("//tbody/tr/td/table/tbody/tr/td[1]/text()")[0].toString();
String path=tntr.evaluateXPath("//tbody/tr/td/table/tbody/tr/td[2]/img/@src")[0].toString();
}
}
到此就可以爬取该页的所有信息。
2、暂时没有好的解决方法,没有能够通过<br>进行定位的语法,有说通过.xpath('string(.)')来解决,试过不行;python貌似可以做的:
tips_lst = []
lst = page.xpath('//div/div')
#提取 1、荷兰豆汆烫变色即可,千万不要过火,时间也就是几秒钟。
print lst[0].text
#依次提取 2, 3, 4
lst = page.xpath('//div/div[@class="tip"]/br')
for ll in lst:
print ll.tail
3、目前打算通过将分类爬取出来存储,然后逐个循环获取每一页的最大页数,然后通过修改url中的分类和页码进行全站爬取。
类似这种/称谓_1__shouyul/、/职务_2__shouyul/。
实际实现的时候遇到了一个莫名其妙的问题:我在获取每一个分类中的最大页码时使用的是类似这样的语句(因为最后最后一项是“尾页”,所以要爬取倒数第二项,而且不能直接申明单个Object对象,要声明为数组,否则TagNode n = (TagNode) last;会报错)
Object last[] = tn.evaluateXPath("//*[@id=\"main_content\"]/table/tbody/tr/td[2]/div/a[last()-1]");
问题是当我直接使用last()时,打印结果显示正常为“尾页”,使用last()+1时,打印输出“首页",使用last()-1时则报如下错误
java.lang.ArrayIndexOutOfBoundsException: 0
个人觉得时last()-n无法使用的,所以换成了稍微复杂的思路,即将手语网中的分页中的a标签全部获取,然后读取对象数组的大小减1即为最大页码的值:
部分代码如下:
Object[] page= tn.evaluateXPath("//*[@id=\"main_content\"]/table/tbody/tr/td[2]/div/a");
int n=page.length-1;
if(n>0) {
for(int i=1;i<=n;i++) {
new SpiderMain().getSpiderData("https://shouyu.51240.com/"+classify+"_"+i+"__shouyul/");
}
}
4、对于数据库的问题仍旧有,当我们没有设置默认的数据库编码格式为utf8时,无法插入汉字,utf8的设置为ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1;
以及当我们在使用insert和update的语法时,我们应该用statement的stat.execute()方法,而不是rs.executeQuery()的方法;否则会报错Can not issue data manipulation statements with executeQuery()。
代码部分:
下载html内容
package com.demo;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
public class DownloadHtml {
/**
* 下载指定URL的Html内容
* @param url url连接
* @return 返回html页面内容
* @throws Exception
*/
public String getHtml(String url) throws Exception{
URL u = new URL(url);
URLConnection conn =u.openConnection();
InputStream in = conn.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(in));
String line = null;
StringBuffer html = new StringBuffer();
while((line=br.readLine())!=null){
html.append(line);
}
in.close();
br.close();
return html.toString();
}
}
Xpath解析
package com.demo;
import org.htmlcleaner.HtmlCleaner;
import org.htmlcleaner.TagNode;
public class SpiderMain {
/**
* 爬虫解析方法
* @param url 待采集的URL
* @return 返回解析的内容
* @throws Exception
*/
public void getSpiderData(String url) throws Exception{
//1.下载html
String html = new DownloadHtml().getHtml(url);
HtmlCleaner hc = new HtmlCleaner();
//2.把html转换成dom对象
TagNode tn = hc.clean(html);
//3.通过xpath解析dom对象
Object[] obje = tn.evaluateXPath("//*[@id=\"main_content\"]/table/tbody/tr/td[2]/table");
if(obje!=null&&obje.length>0) {
for(Object obj:obje) {
TagNode tntr = (TagNode) obj;
String vocabulary=tntr.evaluateXPath("//tbody/tr/td/table/tbody/tr/td[1]/a/strong/text()")[0].toString();
String description=tntr.evaluateXPath("//tbody/tr/td/table/tbody/tr/td[1]/text()")[0].toString();
String path=tntr.evaluateXPath("//tbody/tr/td/table/tbody/tr/td[2]/img/@src")[0].toString();
System.out.println(vocabulary+" "+description+" "+path);
new Shouyu().put(vocabulary, description, path);
}
}
}
public static void main(String[] args) {
try {
new SpiderMain().getSpiderData("https://shouyu.51240.com/称谓_1__shouyul/");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
数据库sql语法:
package com.demo;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class Shouyu {
public void put(String vocabulary,String description,String path) {
String sql="insert into shouyu(vocabulary,description,path)values("
+"'"+vocabulary+"','"+description+"','"+path+"');";
Statement stat=null;
ResultSet rs=null;
Connection conn=new DBHelper().getConn();
try {
stat = conn.createStatement();
stat.execute(sql);
}catch(SQLException e) {
e.printStackTrace();
}
finally {
try {
if(conn!=null) {
conn.close();
}
if(stat!=null) {
stat.close();
}
if(rs!=null) {
rs.close();
}
}catch(SQLException ex) {
ex.printStackTrace();
}
}
}
}
数据库连接池
package com.demo;
import java.sql.*;
public class DBHelper {
String url ="jdbc:mysql://localhost/test?user=root&password=hya8&useUnicod"+ //严格定义,jdbc之后的:也不能少
"e=true&characterEncoding=utf-8" ;
String jdbcName="com.mysql.jdbc.Driver";//驱动程式的名字,是统一的
public Connection getConn(){//connection是一个接口,在java。sql下,用于生成一个连接数据库对象
Connection conn = null;
try{
Class.forName(jdbcName);//将mysql驱动注册到DriverManager中去
}
catch(Exception e){}
try{
conn= DriverManager.getConnection(url); //获得连接对象
}
catch(SQLException ex){}
return conn;
}
public static void main(String[] args)
{
System.out.println(new DBHelper().getConn());
}
}