因為公司需要爬取淘寶的店鋪商品列表,所以研究了下,最后結果是失敗的,技術不行沒辦法,做一個記錄,等待以后有大神搞定。
一、selenium的使用
引入jar包
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-server</artifactId>
</dependency>
同時呢 還需要一個chromeDriver.exe,直接放在項目的目錄下,
先說遇到的問題吧,代碼在最后附上。淘寶的店鋪商品列表需要登錄狀態下才能看到,這就需要有登錄狀態,有了登錄狀態,還是有問題, 當你打開爬取的店鋪頁面,盡在頁面上采用點擊的方式進行翻頁,3-5秒這樣一個頻率是沒問題的,如果你以10秒或10秒一下這樣一個頻率去操作打開不同的店鋪頁面爬取數據,會出現頻繁訪問的彈窗,10秒到15秒隨機等待的話,我試了試貌似能支持很久,這個彈窗需要手動解除滑塊,問題卡就卡在滑塊上了,無論是登陸的滑塊,還是頻繁訪問的滑塊,用驅動器操作都過不了,我也嘗試了滑塊動作的時候速度變化,模擬人手動滑動也不行,而且在測試的時候還發現了另一個問題,滑塊出現的次數多了,會出現封號,甚至封ip的現象。
1、滑塊解決不了 , 2、滑塊頻繁出現后,會封號甚至封ip
這兩個要命的問題直接給我弄崩潰了,可能解決滑塊還是解決不了問題,方向可能就是錯的,應該想辦法讓他不出滑塊 ,說一下前后過程吧。
開始的時候想用2台或多台windows服務器做這個爬取的服務器,登陸靠人工,依靠mq發送消息,只要之后程序能保持登陸狀態就行,這個時候需要解決滑塊問題,
@Slf4j
public class BootStrap {
public static ChromeDriver driver;
static {
try {
//啟動瀏覽器
getDriver();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException {
//啟動消息監聽
// start();
}
@SuppressWarnings("resource")
public static void start() throws InterruptedException {
new ClassPathXmlApplicationContext("spring-activemq.xml");
//等待消息發送
Thread.sleep(1000);
while(true){
Thread.sleep(10 * 60 * 1000);
log.info("keep session ...");
driver.get("https://www.tmall.com/");
}
}
/**
* 獲取 ChromeDriver
* @throws InterruptedException
*/
private static void getDriver() throws InterruptedException{
String os = System.getProperty("os.name");
if (os.toLowerCase().startsWith("win")) {
System.setProperty("webdriver.chrome.driver", System.getProperty("user.dir") + "\\chromedriver_win32\\chromedriver.exe");
} else {
System.setProperty("webdriver.chrome.driver", "/usr/bin/chromedriver");
}
ChromeOptions options = new ChromeOptions();
// 關閉界面上的---Chrome正在受到自動軟件的控制
options.addArguments("--disable-infobars");
// 允許重定向
options.addArguments("--disable-web-security");
// 最大化
options.addArguments("--start-maximized");
options.addArguments("--no-sandbox");
List<String> excludeSwitches = Lists.newArrayList("enable-automation");
options.setExperimentalOption("excludeSwitches", excludeSwitches);
driver = new ChromeDriver(options);
//響應等待時間
driver.manage().timeouts().implicitlyWait(5, TimeUnit.SECONDS);
driver.get("https://login.tmall.com");
Thread.sleep(1000);
while(true) {
if(isLogin()){
break;
}
Thread.sleep(2000);
}
}
/**
* 判斷是否登錄
* @return boolean
*/
private static boolean isLogin(){
Document doc = Jsoup.parse(driver.getPageSource());
if(doc.text().contains("Hi,") || doc.text().contains("Hi!")) {
return true;
}
return false;
}
}
上面是啟動瀏覽器打開登陸頁面,這個時候找到淘寶有三個登陸,一個是天貓的,一個是淘寶的 ,還有一個是淘寶登陸頁里面有個 微博登陸的窗口,淘寶和天貓的都有滑塊,而微博登陸的窗口是圖片驗證碼,如果有道友對圖片驗證碼有信心可以去試試。
這時候用人工掃碼登陸,但是爬取的時候發現頻繁訪問窗口, 嘗試解鎖發現過不去,這個時候發現換個賬號就不會出現解鎖彈窗,就想着如果出現彈窗就 換個賬號重新登錄,這就需要解決自動登錄,事實證明自己太年輕,想的太簡單了,登錄頁面也有滑塊,
但是發現微博登錄淘寶的頁面是驗證碼,可以嘗試下。
先貼擬人滑塊登陸的代碼吧
public class Test {
public static ChromeDriver driver;
public static List<Integer> back_tracks = new ArrayList<>();
static {
try {
//啟動瀏覽器
getDriver();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws IOException, InterruptedException {
// start();
}
/**
* 獲取 ChromeDriver
* @throws InterruptedException
*/
private static void getDriver() throws InterruptedException{
String os = System.getProperty("os.name");
if (os.toLowerCase().startsWith("win")) {
System.setProperty("webdriver.chrome.driver",
System.getProperty("user.dir") + "\\chromedriver_win32\\chromedriver.exe");
} else {
System.setProperty("webdriver.chrome.driver", "/usr/bin/chromedriver");
}
ChromeOptions options = new ChromeOptions();
// 關閉界面上的---Chrome正在受到自動軟件的控制
options.addArguments("--disable-infobars");
// 允許重定向
options.addArguments("--disable-web-security");
// 最大化
options.addArguments("--start-maximized");
options.addArguments("--no-sandbox");
List<String> excludeSwitches = Lists.newArrayList("enable-automation");
options.setExperimentalOption("excludeSwitches", excludeSwitches);
driver = new ChromeDriver(options);
driver.manage().timeouts().implicitlyWait(5, TimeUnit.SECONDS);
driver.get("https://login.taobao.com/member/login.jhtml");
WebElement itemDiv = driver.findElement(By.id("J_Quick2Static"));
itemDiv.click();
WebElement tpl_username_1 = driver.findElement(By.id("TPL_username_1"));
WebElement TPL_password_1 = driver.findElement(By.id("TPL_password_1"));
WebElement nc_1_n1z = driver.findElement(By.id("nc_1_n1z"));
WebElement usernameFeld = driver.findElement(By.className("username-field"));
usernameFeld.click();
tpl_username_1.sendKeys("12");
TPL_password_1.click();
TPL_password_1.sendKeys("12");
Actions actions = new Actions(driver);
actions.clickAndHold(nc_1_n1z);
List<Double> tacks = getTacks();
Random ra =new Random();
for (Double tack : tacks) {
int a = tack.intValue();
int yoffset_random = ra.nextInt(6) - 2;
actions.moveByOffset(a, yoffset_random).perform();
Thread.sleep(20+ ra.nextInt(20));
}
Thread.sleep(200+ ra.nextInt(400));
for (Integer back_track : back_tracks) {
int yoffset_random = ra.nextInt(4) - 2;
actions.moveByOffset(back_track, yoffset_random).perform();
}
int x = ra.nextInt(3) - 4;
int y = ra.nextInt(4) - 2;
actions.moveByOffset(x, y).perform();
x = ra.nextInt(2) +1;
y = ra.nextInt(4) - 2;
actions.moveByOffset(x, y).perform();
Thread.sleep(200+ ra.nextInt(400));
while(true) {
if(isLogin()){
break;
}
Thread.sleep(2000);
}
}
/**
* 判斷是否登錄
* @return boolean
*/
private static boolean isLogin(){
Document doc = Jsoup.parse(driver.getPageSource());
if(doc.text().contains("淘寶賬戶登錄") ) {
return false;
}
return true;
}
/**
* 改變拖拽速度,更加擬人化
* @return
*/
public static List<Double> getTacks(){
List<Double> list = new ArrayList<>();
Random ra =new Random();
int length = 258;
double mid1 = Math.round(length *( Math.random() * 0.1 + 0.1));
double mid2 = Math.round(length *( Math.random() * 0.1 + 0.65));
double mid3 = Math.round(length *( Math.random() * 0.04 + 0.84));
double current = 0;
double a = 0;
double v = 0;
double t = 0.2;
while (current < length){
if(current < mid1){
a = ra.nextInt(5)+ 10;
}else if(current < mid2){
a = ra.nextInt(10) + 30;
}else if(current < mid3){
a = -70;
}else{
a = ra.nextInt(7)-25;
}
double v0 = v;
v = v0 + a * t;
v = v > 0 ? v : 0;
double move = v0 * t + ((a * (t * t))/2);
move = Math.round(move > 0 ? move : 1);
current += move;
list.add(move);
}
double out_range = length - current;
if (out_range < -8){
int sub = ((Double)(out_range + 8)).intValue();
back_tracks.add(-1);
back_tracks.add(sub);
back_tracks.add(-3);
back_tracks.add(-1);
back_tracks.add(-1);
back_tracks.add(-1);
back_tracks.add(-1);
}else if(out_range < -2){
int sub = ((Double)(out_range + 3)).intValue();
back_tracks.add(-1);
back_tracks.add(-1);
back_tracks.add(sub);
}
System.out.println(list);
return list;
}
}
下面貼驗證碼識別的, 我用的是百度的圖片識別,基本是沒有能正確識別的,其他代碼差不多,我就貼截圖和識別的代碼。
/**
* 獲取 ChromeDriver
* @throws InterruptedException
*/
private static void getDriver() throws InterruptedException{
String os = System.getProperty("os.name");
if (os.toLowerCase().startsWith("win")) {
System.setProperty("webdriver.chrome.driver", System.getProperty("user.dir") + "\\chromedriver_win32\\chromedriver.exe");
} else {
System.setProperty("webdriver.chrome.driver", "/usr/bin/chromedriver");
}
ChromeOptions options = new ChromeOptions();
// 關閉界面上的---Chrome正在受到自動軟件的控制
options.addArguments("--disable-infobars");
// 允許重定向
options.addArguments("--disable-web-security");
// 最大化
options.addArguments("--start-maximized");
options.addArguments("--no-sandbox");
List<String> excludeSwitches = Lists.newArrayList("enable-automation");
options.setExperimentalOption("excludeSwitches", excludeSwitches);
driver = new ChromeDriver(options);
//響應等待時間
driver.manage().timeouts().implicitlyWait(5, TimeUnit.SECONDS);
// driver.get("https://login.tmall.com");
driver.get("https://login.taobao.com/member/login.jhtml");
WebElement itemDiv = driver.findElement(By.id("J_Quick2Static"));
itemDiv.click();
WebElement element = driver.findElement(By.className("weibo-login"));
element.click();
Thread.sleep(1000);
//微博登陸
Document doc = Jsoup.parse(driver.getPageSource());
if(doc.text().contains("快速登錄")) {
WebElement spanBg = driver.findElementByCssSelector(".btn_tip > .W_btn_g > span");
spanBg.click();
}else{
WebElement username = driver.findElement(By.name("username"));
WebElement password = driver.findElement(By.name("password"));
Actions action = new Actions(driver);
action.click(username).perform();
username.clear();
username.sendKeys("");
action.click(password).perform();
password.clear();
password.sendKeys("");
Thread.sleep(2000);
}
//截取整個頁面
File screenShot = driver.getScreenshotAs(OutputType.FILE);
try {
String name = "C:\\Users\\Administrator\\Desktop\\image\\"+System.currentTimeMillis()+".jpg";
FileUtils.copyFile(screenShot,new File(name));
//找到驗證碼位置,截取驗證碼
WebElement code = driver.findElementByCssSelector(".code > img");
Point location = code.getLocation();
Dimension size = code.getSize();
Image img = Toolkit.getDefaultToolkit().getImage(name);
BufferedImage read = toBufferedImage(img);
BufferedImage subimage = read.getSubimage(location.getX(), location.getY(), size.getWidth(), size.getHeight());
String newName = "C:\\Users\\Administrator\\Desktop\\image\\"+System.currentTimeMillis()+".jpg";
System.out.println(newName);
ImageIO.write(subimage, "jpg", new File(newName));
//使用百度圖片識別
AipOcr client = new AipOcr("11", "22", "33");
client.setConnectionTimeoutInMillis(2000);
client.setSocketTimeoutInMillis(60000);
HashMap<String, String> param = new HashMap<>(16);
param.put("detect_direction", "true");
param.put("probability", "true");
JSONObject res = client.basicAccurateGeneral(newName, param);
System.out.println(res.toString(2));
} catch (IOException e) {
e.printStackTrace();
}
Thread.sleep(1000);
while(true) {
if(isLogin()){
break;
}
Thread.sleep(2000);
}
}
public static BufferedImage toBufferedImage(Image image) {
if (image instanceof BufferedImage) {
return (BufferedImage)image;
}
image = new ImageIcon(image).getImage();
BufferedImage bimage = null;
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
try {
int transparency = Transparency.OPAQUE;
GraphicsDevice gs = ge.getDefaultScreenDevice();
GraphicsConfiguration gc = gs.getDefaultConfiguration();
bimage = gc.createCompatibleImage(
image.getWidth(null), image.getHeight(null), transparency);
} catch (HeadlessException e) {
// The system does not have a screen
}
if (bimage == null) {
// Create a buffered image using the default color model
int type = BufferedImage.TYPE_INT_RGB;
bimage = new BufferedImage(image.getWidth(null), image.getHeight(null), type);
}
Graphics g = bimage.createGraphics();
g.drawImage(image, 0, 0, null);
g.dispose();
return bimage;
}
這里我做了整個頁面的截圖 ,然后又把驗證碼單獨截取出來,因為驗證碼的這張圖片它不正經。
最后驗證碼識別成功率很低,正想換個識別驗證碼的方法,結果這個時候發現了封ip的現象,這個時候我雙手離開鍵盤了,mmp。
現在想了想,這條路可能就不對,當然我技術低微,說不定有大神能解決。
未去實踐的想法
1、覺得應該想辦法讓他不出滑塊,找到一個合適的策略將每個服務器的爬取頻率盡可能的放大, 這樣也許能滿足需求,因為一個店鋪頁面內,你僅僅是點擊翻頁,是問題不大的,我的業務需求是我的頁面和淘寶頁面展示一樣的商品,用戶點擊翻頁,我去淘寶爬取數據,那我可以一次性的多拿幾頁,比如拿10頁,盡可能的給服務器創造一個最大的爬取間隔,個人感覺只要超過10秒,很有可能長時間不出現彈窗。
2、可以嘗試在app端做點事情,不知道移動端有沒有滑塊,當然也需要解決登錄問題。
