iT邦幫忙

2024 iThome 鐵人賽

DAY 27
0
Software Development

我命由我不由語言 java爬蟲挑戰系列 第 27

java爬蟲挑戰 Day 27 - 實作爬取免費Proxy並使用

  • 分享至 

  • xImage
  •  

昨天已經介紹過為什麼要使用Proxy了,今天就直接實作。

分析FreeProxyList

https://free-proxy-list.net

要將下圖中的ip、port存下來使用
https://ithelp.ithome.com.tw/upload/images/20240912/201686352Juj0BU9C8.png

右鍵=>檢視原始碼 就可以看到相關資訊了,所以是靜態網頁,直接用Jsop就好了。
取得Dom物件後,將Table裡的資料取出即可。

程式實作

建立資料庫Table

新增Proxy Entity

@Entity
@Data
public class Proxy {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    private String ip;
    private int port;

    public Proxy(String ip, int port) {
        this.ip = ip;
        this.port = port;
        this.active = true;
    }
}

新增Proxy Repository

public interface ProxyRepository extends JpaRepository<Proxy, Long> {
}

編寫Service邏輯

新增ProxyCrawlerService介面

public interface ProxyCrawlerService {
    List<Proxy> getProxies() throws IOException;
}

實作ProxyCrawlerServiceImpl

主要的邏輯都在這邊了,簡單說明一下要做甚麼。

  • Jsop 抓取 https://www.free-proxy-list.net/ 的DOM回來
  • 將Table中的IP、Port、能連上Goole、有Https等的資料抓回來
  • 在使用剛剛的資料中的Proxy直接訪問591,訪問OK的在存下來。
  • 將OK的資料返回給排程,排程再將OK的資料寫入資料庫(這段邏輯在UpdateProxyScheduler)
@Override
    public List<Proxy> getProxies() throws IOException {
        String url = "https://www.free-proxy-list.net/";

        List<Proxy> proxies = new ArrayList<>();
        Document doc = Jsoup.connect(url).get();
        // 找到代理表格的 tbody 部分
        Element tableBody = doc.select("table.table tbody").first();

        if (tableBody != null) {

            // 取得表格中的每一行
            Elements rows = tableBody.select("tr");
            // 遍歷每一行,並擷取數據
            for (Element row : rows) {
                Elements cells = row.select("td");

                // 如果列數符合表格的預期
                if (cells.size() >= 8) {
                    String ipAddress = cells.get(0).text();
                    String port = cells.get(1).text();
                    String country = cells.get(3).text();
                    String anonymity = cells.get(4).text();
                    String google = cells.get(5).text();
                    String https = cells.get(6).text();
                    String lastChecked = cells.get(7).text();
                    if ("yes".equals(google) || "yes".equals(https))
                        proxies.add(new Proxy(ipAddress, Integer.parseInt(port)));

                }
            }
        } else {
            System.out.println("表格找不到!");
        }

        log.info("web say google ok or https ok proxy count is :{}", proxies.size());
        List<Proxy> collect = proxies.parallelStream().filter(pro -> isProxyValid(pro.getIp(), pro.getPort()))
                .collect(Collectors.toList());

        log.info("ok proxy count is :{}", collect.size());
        return collect;
    }

    public boolean isProxyValid(String ip, int port) {
        try {
            java.net.Proxy proxy = new java.net.Proxy(java.net.Proxy.Type.HTTP, new InetSocketAddress(ip, port));
            URL url = new URL("https://rent.591.com.tw/list");
            HttpURLConnection connection = (HttpURLConnection) url.openConnection(proxy);
            connection.setConnectTimeout(5000); // 5 seconds timeout
            connection.connect();

            boolean b = connection.getResponseCode() == 200;
            log.info("isProxyValid:{}", b ? "OK" : "Fail");
            log.info("ip:{}:{}", ip, String.valueOf(port));

            return b;
        } catch (IOException e) {

            log.info("isProxyValid:{}", "timeout");
            return false;
        }
    }

建立排程,定期更新Proxy

parameter.properties新增參數

新預計8小時更新一次Proxy

proxy.update.schedule=0 0 0/8 * * ?

新增UpdateProxyScheduler排程

總之就是時間到了,就去爬取免費Proxy,然後存到DB中。

@Service
public class UpdateProxyScheduler {

    @Autowired
    ProxyCrawlerService proxyCrawlerService;
    
    @Autowired
    ProxyRepository proxyRepository;

    @PostConstruct
    public void runOnceAtStartup() {
        updateProxySchedule();
    }

    @Scheduled(cron = "${proxy.update.schedule}")
    public void scheduledCrawl() throws Exception {
        updateProxySchedule();
    }

    private void updateProxySchedule() {
        List<Proxy> proxies = Collections.emptyList();
        try {
            proxies = proxyCrawlerService.getProxies();
        } catch (IOException e) {
            e.printStackTrace();
        }
        proxyRepository.saveAll(proxies);
    }

}

591爬蟲改用Proxy

調整RentalCrawlerServiceImpl

先使用ProxyRepository

@Autowired
private ProxyRepository proxyRepository;

調整方法=>getJsoupDoc,之前是Jsop直接訪問591,現在使用Jsopproxy方法,指定使用資料庫中的隨機proxy。
並且失敗的話,回從新再執行一次該方法。 (應該要設個失敗上限次數,偷懶沒寫)

private Document getJsoupDoc(String urlString) {

    List<Proxy> proxies = proxyRepository.findAll();
    Proxy randomProxy = proxies.get(new Random().nextInt(proxies.size()));

    log.info("use proxy ip:{}:{}", randomProxy.getIp(), String.valueOf(randomProxy.getPort()));

    Connection connection = Jsoup.connect(urlString)
            .proxy(randomProxy.getIp(), randomProxy.getPort())
            .timeout(10000);

    // Add headers or user-agent if needed
    Document doc = null;
    try {
        doc = connection.get();
        log.info("use proxy ip:{}:{} => OK", randomProxy.getIp(), String.valueOf(randomProxy.getPort()));
        return doc;
    } catch (IOException e) {
        log.error("proxy連線異常,message:{}", e);
        return getJsoupDoc(urlString);
    }

}

成果展示

FreeProxyList提供了300個免費Proxy,我只使用可以連上google或https的,共38個。
https://ithelp.ithome.com.tw/upload/images/20240913/20168635x0qUJclJaN.png

結果只有3個,傻眼。 (最慘的時候只有一個能用
https://ithelp.ithome.com.tw/upload/images/20240913/20168635FQC42IU3br.png

成功使用proxy取得591資料
https://ithelp.ithome.com.tw/upload/images/20240913/20168635igvxoFLBLR.png

git現狀

https://github.com/a951753sxd/rental-crawler
https://ithelp.ithome.com.tw/upload/images/20240913/20168635vDXeNPM5Ia.png

小結

今天成功爬取免費Proxy並存入資料庫中供591爬蟲模組使用,今天的實作也是意義重大,可以說是把第5~12天的進度透過不同的目標又進行了一次實作。

程式開發開發真的告一個段落了,接下來的三天就打打字,聊聊一些我的心得吧XD。


上一篇
java爬蟲挑戰 Day 26 - 從 IP 封鎖到 Proxy 的應用
下一篇
java爬蟲挑戰 Day 28 - Java 與 Python的經驗分享與語言比較
系列文
我命由我不由語言 java爬蟲挑戰30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言