Eclipse使用Java Selenium抓取眾籌網站的資料
Selenium簡介
百度百科
Selenium 是一個用於Web應用程式測試的工具。Selenium測試直接執行在瀏覽器中,就像真正的使用者在操作一樣。支援的瀏覽器包括IE(7, 8, 9, 10, 11),Mozilla Firefox,Safari,Google Chrome,Opera等。這個工具的主要功能包括:測試與瀏覽器的相容性——測試你的應用程式看是否能夠很好得工作在不同瀏覽器和作業系統之上。測試系統功能——建立迴歸測試檢驗軟體功能和使用者需求。支援自動錄製動作和自動生成 .Net、Java、Perl等不同語言的測試指令碼。
下載地址
我使用的是Chrome瀏覽器進行資料抓取,不同版本的Chrome需要下載對應的Selenium包。檢視自己的Chrome版本可以在右上角的幫助->關於Chrome中看到。具體的Selenium下載地址這裡也幫讀者附上:
目標網站
這次我需要爬取的網站為疾病眾籌網站–輕鬆籌,在主頁上有25個不同的展示視窗,存放了25個不同的案例。我需要獲得這25個不同的案例的具體資訊,跟蹤記錄每一個案例的後續情況(後續案例可能不在首頁出現,但是仍然可以有url存在,專案會繼續傳播,愛心人士可以繼續捐款)。 點選每一個案例的具體情況是這樣的頁面,我會抓取每一個具體案例的不同資訊,如標題、發起人姓名、目標金額、獲得幫助次數等。
程式碼實現
整體架構
DAO層 ————負責連結資料庫與資料庫中表的操作方法 Model層————負責實體資料模型實現 Selenium層———負責具體資料的抓取 UrlManage層———負責管理每個專案的URL屬於輔助包,後續沒有繼續應用
程式碼
DAO層
DAO層中有兩個類 LinkDB負責Eclispe與Mysql的連線 TableManage負責具體資料庫中表的操作
LinkDB類
package DAO; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; public class LinkDB { public static Connection conn=null; public static Statement stmt=null; public LinkDB() { try { Class.forName("com.mysql.jdbc.Driver"); System.out.println("成功連線到資料庫!"); conn= DriverManager.getConnection( "jdbc:mysql://localhost:3306/qsc","root","123456"); stmt=conn.createStatement(); }catch(ClassNotFoundException e) { e.printStackTrace(); }catch(SQLException e) { e.printStackTrace(); } } }
TableManage類
package DAO;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import Model.Belongings;
import Model.Classifiers;
import Model.DongTai;
import Model.Proofment;
import Model.QscProject;
import Model.TopHelper;
public class TableManage {
/*
* 建立指定名稱的表
* tablename stmt
*/
public String CreateTable(String tablename,Statement stmt)
{
String creatsql = "CREATE TABLE "+tablename+"("
+ "title varchar(255) not null,"+"finished int(10),"+
"name varchar(255),"+"target varchar(255),"+
"already varchar(255),"+"helptimes varchar(255),"
+"date varchar(255),"+"des varchar(3000),"+"url varchar(300),"
+"zhuanfa varchar(255),"+"inindex int(10)"+")";
try {
stmt.executeLargeUpdate(creatsql);
System.out.println("建立表"+tablename+"成功!");
}catch(Exception e)
{
e.printStackTrace();
}
return tablename;
}
/*
* 完結的專案創捷Helper的儲存表
*/
public String CreateHelperTable(String tablename,Statement stmt)
{
String creatsql = "CREATE TABLE "+tablename+"("
+ "name varchar(255) not null,"+
"money varchar(255),"+"people_bring varchar(255)"+
")";
try {
stmt.executeLargeUpdate(creatsql);
System.out.println("建立表"+tablename+"成功!");
}catch(Exception e)
{
e.printStackTrace();
}
return tablename;
}
/*
* 向Helper表中插入資料
*/
public void InsertToHelper(List<TopHelper> helpers,String tablename,Connection conn)
{
for(int i=0;i<helpers.size();i++)
{
try {
String sql= "INSERT INTO `qsc`."+"`"+tablename+"`"+" (`name`, `money`, `people_bring`) VALUES (?,?,?);";
PreparedStatement ps=conn.prepareStatement(sql);
ps.setString(1, helpers.get(i).getName());
ps.setString(2, helpers.get(i).getMoney());
ps.setString(3, helpers.get(i).getPeoplebring());
ps.executeUpdate();
System.out.println("專案"+tablename+"更新幫助者表單資訊");
}catch(Exception e)
{
e.printStackTrace();
}
}
}
/*
* 向指定表中新增案例當前情況
* 變更情況應當只有金額與時間
*/
public int addtotable(QscProject qsc,String tablename,Connection conn) {
try {
String sql= "INSERT INTO `qsc`."+"`"+tablename+"`"+" (`title`, `name`, `target`, `already`, `helptimes`, `date`, `des`,`url`,`zhuanfa`,`finished`,`inindex`) VALUES (?,?, ?, ?, ?, ?, ?, ?,?,?,?);";
PreparedStatement ps=conn.prepareStatement(sql);
ps.setString(1, qsc.getTitle());
ps.setString(2, qsc.getName());
ps.setString(3, qsc.getTarget());
ps.setString(4, qsc.getAlreadyget());
ps.setString(5,qsc.getHelptimes());
ps.setString(6, qsc.getDate());
ps.setString(7, qsc.getDesciption());
ps.setString(8, qsc.getUrl());
ps.setString(9, qsc.getZhuanfa());
ps.setInt(10, qsc.getIf_finish());
ps.setInt(11, qsc.getInindex());
ps.executeUpdate();
}catch(Exception e)
{
e.printStackTrace();
}
System.out.println("專案"+qsc.getName()+"資料更新");
return -1;
}
/*
* 向url總表中新增新的案例
*/
public int add(String url,Connection conn,String name) {
try {
//注意後期建立表格
String sql= "INSERT INTO `qsc`.`qsc_allurls` (`url`,`finished`,`name`) VALUES (?,?,?);";
PreparedStatement ps=conn.prepareStatement(sql);
ps.setString(1, url);
ps.setInt(2, 0);
ps.setString(3,name);
return ps.executeUpdate();
}catch(Exception e)
{
e.printStackTrace();
}
return -1;
}
/*
* 獲取當前案例總表中的全部URL
*/
public List<String> GetUrl(String tablename,Connection conn)
{
List<String> urls=new ArrayList<String>();
try {
String presql="select url from"+" `qsc`."+"`"+tablename+"`"+"where finished=0";
PreparedStatement ps=conn.prepareStatement(presql);
ResultSet res=ps.executeQuery();
while(res.next()) {
urls.add(res.getString(1));
}
System.out.println("已經獲取未完成案例全部url");
}catch(Exception e)
{
e.printStackTrace();
}
return urls;
}
/*
* 判斷專案是否已經結束,取是否結束欄位判斷
*/
public boolean IfProjectFinished(String url,Connection conn)
{
boolean result=false;
try {
String presql="select finished from `qsc`.`qsc_allurls` where url ="+url;
PreparedStatement ps=conn.prepareStatement(presql);
ResultSet res=ps.executeQuery();
if(res.getInt(1)==1)
{
result=true;
}
}catch(Exception e)
{
e.printStackTrace();
}
return result;
}
/*
* 插入名字
*/
public void InsertTheName(String url,Connection conn,String name)
{
try {
String presql="update `qsc`.`qsc_allurls` set name=? where url=?";
PreparedStatement pst=conn.prepareStatement(presql);
pst.setString(1, name);
pst.setString(2, url);
pst.executeUpdate();
}catch(Exception e)
{
e.printStackTrace();
}
}
/*
* 改變結束狀態
*/
public void ChangeFinished(String url,Connection conn)
{
try {
String presql="update `qsc`.`qsc_allurls` set finished=? where url=?";
PreparedStatement pst=conn.prepareStatement(presql);
pst.setInt(1, 1);
pst.setString(2, url);
pst.executeUpdate();
}catch(Exception e)
{
e.printStackTrace();
}
}
/*
* 獲取專案ID
*/
public List<String> GetNames(String tablename,Connection conn)
{
List<String> names=new ArrayList<String>();
try {
String presql="select name from"+" `qsc`."+"`"+tablename+"`";
PreparedStatement ps=conn.prepareStatement(presql);
ResultSet res=ps.executeQuery();
while(res.next()) {
names.add(res.getString(1));
}
System.out.println("已經獲取全部url");
}catch(Exception e)
{
e.printStackTrace();
}
return names;
}
/*
* 建立證明資料的表
*/
public String CreateProofTable(String tablename,Statement stmt)
{
String creatsql = "CREATE TABLE "+tablename+"("
+ "patient varchar(255) not null,"+"patient_des varchar(255),"+"illness varchar(255),"+"illness_des varchar(255),"+
"moneygetter varchar(255),"+"moneygetter_des varchar(255)"+
")";
try {
stmt.executeLargeUpdate(creatsql);
System.out.println("建立表"+tablename+"成功!");
}catch(Exception e)
{
e.printStackTrace();
}
return tablename;
}
/*
* 向證明表中插入資訊
*/
public void AddToProof(Proofment proof,Connection conn,String tablename)
{
try {
String sql= "INSERT INTO `qsc`."+"`"+tablename+"`"+" (`patient`, `patient_des`, `illness`, `illness_des`, `moneygetter`, `moneygetter_des`) VALUES (?,?, ?, ?, ?, ?);";
PreparedStatement ps=conn.prepareStatement(sql);
ps.setString(1, proof.getPatient());
ps.setString(2, proof.getPatient_des());
ps.setString(3, proof.getIllness());
ps.setString(4, proof.getIllness_des());
ps.setString(5, proof.getMoneygetter());
ps.setString(6, proof.getMoneygetter_des());
ps.executeUpdate();
System.out.println("插入了證明資訊");
}catch(Exception e)
{
e.printStackTrace();
}
}
/*
* 建立財產表
*/
public String CreateBelongTable(String tablename,Statement stmt)
{
String creatsql = "CREATE TABLE "+tablename+"("
+ "house varchar(255) not null,"+"cars varchar(255),"+"insurance varchar(255)"+
")";
try {
stmt.executeLargeUpdate(creatsql);
System.out.println("建立表"+tablename+"成功!");
}catch(Exception e)
{
e.printStackTrace();
}
return tablename;
}
/*
* 插入財產資料
*/
public void AddToBelong(Belongings belong,Connection conn,String tablename)
{
try {
String sql= "INSERT INTO `qsc`."+"`"+tablename+"`"+" (`house`, `cars`, `insurance`) VALUES (?,?, ?);";
PreparedStatement ps=conn.prepareStatement(sql);
ps.setString(1, belong.getHouse());
ps.setString(2, belong.getCars());
ps.setString(3, belong.getInsurance());
ps.executeUpdate();
System.out.println("插入了財產資訊");
}catch(Exception e)
{
e.printStackTrace();
}
}
/*
* 建立動態表
*/
public String CreateDongTaiTable(String tablename,Statement stmt)
{
String creatsql = "CREATE TABLE "+tablename+"("
+ "name varchar(255) not null,"+"text varchar(255),"+"date varchar(255),"+
"catchdate varchar(255),"+"des varchar(500)"+")";
try {
stmt.executeLargeUpdate(creatsql);
System.out.println("建立表"+tablename+"成功!");
}catch(Exception e)
{
e.printStackTrace();
}
return tablename;
}
/*
* 向動態表中插入資料
*/
public void AddToDongTai(List<DongTai> dongtais,Connection conn,String tablename)
{
for(int i=0;i<dongtais.size();i++)
{
try {
String sql= "INSERT INTO `qsc`."+"`"+tablename+"`"+" (`name`, `text`, `date`,`catchdate`,`des`) VALUES (?,?,?,?,?);";
PreparedStatement ps=conn.prepareStatement(sql);
ps.setString(1, dongtais.get(i).getName());
ps.setString(2, dongtais.get(i).getText());
ps.setString(3, dongtais.get(i).getDate());
ps.setString(4, dongtais.get(i).getCatchdate());
ps.setString(5, dongtais.get(i).getDes());
ps.executeUpdate();
System.out.println("插入了動態資訊");
}catch(Exception e)
{
e.printStackTrace();
}
}
}
/*
* 建立證明人表
*/
public String CreateClassifiersTable(String tablename,Statement stmt)
{
String creatsql = "CREATE TABLE "+tablename+"("
+ "name varchar(255) not null,"+"des varchar(255),"+"time varchar(255),"+
"relation varchar(255),"+"text2 varchar(500)"+")";
try {
stmt.executeLargeUpdate(creatsql);
System.out.println("建立表"+tablename+"成功!");
}catch(Exception e)
{
e.printStackTrace();
}
return tablename;
}
/*
* 插入證明人資訊
*/
public void AddToClassifiers(List<Classifiers> classifiers,Connection conn,String tablename)
{
for(int i=0;i<classifiers.size();i++)
{
try {
String sql= "INSERT INTO `qsc`."+"`"+tablename+"`"+" (`name`, `des`, `time`,`relation`,`text2`) VALUES (?,?,?,?,?);";
PreparedStatement ps=conn.prepareStatement(sql);
ps.setString(1, classifiers.get(i).getName());
ps.setString(2, classifiers.get(i).getDes());
ps.setString(3, classifiers.get(i).getTime());
ps.setString(4, classifiers.get(i).getText1());
ps.setString(5, classifiers.get(i).getText2());
ps.executeUpdate();
}catch(Exception e)
{
e.printStackTrace();
}
}
}
}
Model層
Model層負責建立專案資料結構物件,自我感覺像是在寫JSP中的JavaBean。主要實體類為QscProject。內設了一些我需要儲存的欄位,籌款是否完成,專案是否在網站的首頁等等。
package Model;
import java.util.List;
public class QscProject {
private String name;
private String title;
private String date;
private String desciption;
private String phurl;
private String target;
private String alreadyget;
private String helptimes;
private String url;
private String zhuanfa;
private int if_finish;
private int inindex;
private List<TopHelper> helpers;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getDate() {
return date;
}
public void setDate(String date) {
this.date = date;
}
public String getDesciption() {
return desciption;
}
public void setDesciption(String desciption) {
this.desciption = desciption;
}
public String getPhurl() {
return phurl;
}
public void setPhurl(String phurl) {
this.phurl = phurl;
}
public String getTarget() {
return target;
}
public void setTarget(String target) {
this.target = target;
}
public String getAlreadyget() {
return alreadyget;
}
public void setAlreadyget(String alreadyget) {
this.alreadyget = alreadyget;
}
public String getHelptimes() {
return helptimes;
}
public void setHelptimes(String helptimes) {
this.helptimes = helptimes;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getZhuanfa() {
return zhuanfa;
}
public void setZhuanfa(String zhuanfa) {
this.zhuanfa = zhuanfa;
}
public int getIf_finish() {
return if_finish;
}
public void setIf_finish(int if_finish) {
this.if_finish = if_finish;
}
public List<TopHelper> getHelpers() {
return helpers;
}
public void setHelpers(List<TopHelper> helpers) {
this.helpers = helpers;
}
public int getInindex() {
return inindex;
}
public void setInindex(int inindex) {
this.inindex = inindex;
}
}
Selenium包
重點來了,Selenium包中我只寫了一個Getter類,類中有按照需求寫的一些方法。 先來看類中的引用包和屬性,類中直接設定了兩個靜態量,LinkDB和TableManage,負責連線資料庫和讀寫表。
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.openqa.selenium.By;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import UrlManage.*;
import DAO.LinkDB;
import DAO.TableManage;
import Model.QscProject;
import Model.TopHelper;
import Model.*
;public class Getter {
private static LinkDB mylink=new LinkDB();
private static TableManage tablemanage=new TableManage();
用於獲取首頁的專案的方法GetIndex(),方法返回了一個String的List,目的是為了後續判斷已經存在資料庫中的專案在更新時,判斷還是否存在在首頁上面。 程式碼中設定Selenium的配置程式碼。driver.get(url)為瀏覽器開啟目標網頁。
WebDriver driver;
System.setProperty("webdriver.chrome.driver", "D:\\chromedriver_win32\\chromedriver.exe");
driver =new ChromeDriver();
driver.get("https://m2.qschou.com/index_v7_3.html");
GetIndex()全部程式碼
public List<String> GetIndexUrl(){
List<String> namesinindex=new ArrayList<String>();
WebDriver driver;
System.setProperty("webdriver.chrome.driver", "D:\\chromedriver_win32\\chromedriver.exe");
driver =new ChromeDriver();
driver.get("https://m2.qschou.com/index_v7_3.html");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
List<WebElement> webElement = driver.findElements(By.xpath("/html/body/div[1]/div[3]/div[2]/div/a"));
List<WebElement> webElementName = driver.findElements(By.xpath("/html/body/div[1]/div[3]/div[2]/div/div/a/span[2]"));
List<String> NamesGetted=new ArrayList<String>(tablemanage.GetNames("qsc_allurls", mylink.conn));
if( webElement!=null)
{
for(int i=0;i<webElement.size();i++)
{
namesinindex.add(webElementName.get(i).getText());
if(NamesGetted.contains(webElementName.get(i).getText()))
{
System.out.println("專案"+webElementName.get(i).getText()+"存在");
continue;
}
else {
//ADD to url 表
System.out.println("發現新專案—"+webElementName.get(i).getText());
tablemanage.add(webElement.get(i).getAttribute("href"), mylink.conn,webElementName.get(i).getText());
//建立表
tablemanage.CreateTable("qsc_"+webElementName.get(i).getText(), mylink.stmt);
}
}
}
}catch(Exception e) {
e.printStackTrace();
}
driver.close();
return namesinindex;
}
更新已存在專案的方法
public void UpDateProject(List<String> namesinindex)
{
WebDriver driver;
System.setProperty("webdriver.chrome.driver", "D:\\chromedriver_win32\\chromedriver.exe");
driver =new ChromeDriver();
List<String> UrlsGetted=tablemanage.GetUrl("qsc_allurls", mylink.conn);
try {
for(int i=0;i<UrlsGetted.size();i++)
{
driver.get(UrlsGetted.get(i));
System.out.println("正在訪問"+UrlsGetted.get(i));
Thread.sleep(10000);
QscProject temp=new QscProject();
if(IfFinished(driver))
{
WebElement title=driver.findElement(By.xpath("/html/body/div[2]/div[3]/header/h1"));
WebElement target=driver.findElement(By.xpath("/html/body/div[2]/div[3]/section[1]/ul/li[1]/strong"));
WebElement helptimes=driver.findElement(By.xpath("/html/body/div[2]/div[3]/section[1]/ul/li[3]/strong"));
WebElement already=driver.findElement(By.xpath("/html/body/div[2]/div[3]/section[1]/ul/li[2]/strong"));
WebElement des=driver.findElement(By.xpath("/html/body/div[2]/div[3]/section[4]/article/section[1]/div[1]"));
WebElement name=driver.findElement(By.xpath("/html/body/div[2]/div[3]/header/div[1]/div/span"));
WebElement helpeurl=driver.findElement(By.xpath("//*[@id=\"love_list\"]"));
String thehelperurl=helpeurl.getAttribute("href");
tablemanage.CreateHelperTable("qsc_"+name.getText()+"_helper", mylink.stmt);
tablemanage.InsertToHelper(GetHelpers(thehelperurl),"qsc_"+name.getText()+"_helper" ,mylink.conn);
temp.setIf_finish(1);
tablemanage.ChangeFinished(UrlsGetted.get(i), mylink.conn);
temp.setName(name.getText());
temp.setTitle(title.getText());
temp.setTarget(target.getText());
temp.setAlreadyget(already.getText());
temp.setHelptimes(helptimes.getText());
temp.setDesciption(des.getText());
temp.setDate(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss aa").format(new Date()));
temp.setUrl(UrlsGetted.get(i));
if(namesinindex.contains(name.getText()))
{
temp.setInindex(1);
}else {
temp.setInindex(0);
}
GetClassifiers(GetOthers(UrlsGetted.get(i), name.getText()), name.getText());
}else {
WebElement title=driver.findElement(By.xpath("/html/body/div[2]/div[3]/header/h1"));
WebElement target=driver.findElement(By.xpath("/html/body/div[2]/div[3]/section[1]/ul/li[1]/strong"));
WebElement helptimes=driver.findElement(By.xpath("/html/body/div[2]/div[3]/section[1]/ul/li[3]/strong"));
WebElement already=driver.findElement(By.xpath("/html/body/div[2]/div[3]/section[1]/ul/li[2]/strong"));
WebElement des=driver.findElement(By.xpath("/html/body/div[2]/div[3]/section[4]/article/section[1]/div[1]"));
WebElement name=driver.findElement(By.xpath("/html/body/div[2]/div[3]/header/div[1]/div/span"));
WebElement zhuanfa=driver.findElement(By.xpath("//*[@id=\"go_share\"]/div/span"));
temp.setIf_finish(0);
temp.setName(name.getText());
temp.setTitle(title.getText());
temp.setTarget(target.getText());
temp.setAlreadyget(already.getText());
temp.setHelptimes(helptimes.getText());
temp.setDesciption(des.getText());
temp.setDate(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss aa").format(new Date()));
temp.setUrl(UrlsGetted.get(i));
temp.setZhuanfa(zhuanfa.getText());
if(namesinindex.contains(name.getText()))
{
temp.setInindex(1);
}else {
temp.setInindex(0);
}
}
tablemanage.addtotable(temp, "qsc_"+temp.getName(), mylink.conn);
}
}catch(Exception e)
{
e.printStackTrace();
}
driver.close();
}
經驗教訓
Xpath與正則表示式
在這次的資料爬取中,我刻意迴避了正則表示式的使用(因為我不會),全程使用Xpath定位網頁元素。定位方法為,滑鼠移動到目標元素上後右鍵,點選審查元素。
隨後在網頁原始碼中點選Copy Xpath即可。
關於無法定位元素
有時候會出現無法定位到目標Xpath的情況,這時候原因有如下的可能: 1.頁面需要載入,還沒有載入完全部的元素。 解決方法:設定程式等待即可。 2.元素定位出現問題,需要滾動網頁的滾動條。 解決方法:滾動滾動條即可。 下面的程式碼給出了設定等待和自動滾動滾動條的程式碼。等待了2000毫秒,滾動了一個滾動條的長度。
Thread.sleep(2000);
((JavascriptExecutor) driver).executeScript("window.scrollTo(0, document.body.scrollHeight)");
Thread.sleep(2000);
((JavascriptExecutor) driver).executeScript("window.scrollTo(0, document.body.scrollHeight)");
Thread.sleep(2000);
((JavascriptExecutor) driver).executeScript("window.scrollTo(0, document.body.scrollHeight)");
持續抓取過程中執行緒問題
在我的專案中,我需要持續迴圈執行抓取程式。main方法中的執行緒不會自動回收,記得將不用的物件及時指向null,並且定期執行系統的垃圾回收。
public static void main(String[] args)
{
int count=0;
while(true){
count++;
Getter test=new Getter();
test.UpDateProject(test.GetIndexUrl());
test=null;
if(count==100)
{
System.gc();
count=0;
}
}
伺服器租用與程式設定
這次使用了騰訊雲,系統為Windows Server。我只是簡單的複製了在本機的操作環境,將Eclipse中的程式簡單的移植過去,這種方法較low,請大家不要學習。