JavaFX 小说下载器 - 自动抓取网络小说
import javafx.application.Application; import javafx.application.Platform; import javafx.concurrent.Task; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.Scene; import javafx.scene.control.; import javafx.scene.layout.; import javafx.stage.Stage; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.select.Elements; import org.openqa.selenium.*; import org.openqa.selenium.chrome.ChromeDriver; import org.openqa.selenium.chrome.ChromeOptions; import org.openqa.selenium.support.ui.ExpectedConditions; import org.openqa.selenium.support.ui.WebDriverWait;
import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors;
public class NovelDownloader extends Application {
private Map<String, String> chapters = new HashMap<>();
private TextField novelNameInput;
private CheckBox saveCheckBox;
private ListView
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage primaryStage) {
primaryStage.setTitle('小说下载器');
novelNameInput = new TextField();
saveCheckBox = new CheckBox('是否下载小说内容到当前目录下');
Button searchButton = new Button('获取');
chapterList = new ListView<>();
chapterText = new TextArea();
chapterText.setEditable(false); // 设置内容区域不可编辑
VBox root = new VBox(10);
root.setPadding(new Insets(10));
HBox inputBox = new HBox(10);
HBox.setHgrow(novelNameInput, Priority.ALWAYS);
inputBox.getChildren().addAll(novelNameInput, saveCheckBox, searchButton);
// 使用 GridPane 布局章节列表和内容区域
GridPane gridPane = new GridPane();
gridPane.setHgap(10);
gridPane.setVgap(10);
gridPane.setAlignment(Pos.CENTER);
ColumnConstraints column1 = new ColumnConstraints();
column1.setPercentWidth(30); // 章节列表占 30% 宽度
ColumnConstraints column2 = new ColumnConstraints();
column2.setPercentWidth(70); // 内容区域占 70% 宽度
gridPane.getColumnConstraints().addAll(column1, column2);
gridPane.add(chapterList, 0, 0);
gridPane.add(chapterText, 1, 0);
root.getChildren().addAll(new Label('请输入小说名称:'), inputBox, gridPane);
searchButton.setOnAction(event -> {
String novelName = novelNameInput.getText();
if (!novelName.isEmpty()) {
searchNovelAndScrape(novelName);
} else {
showAlert('警告', '请输入小说名称', Alert.AlertType.WARNING);
}
});
chapterList.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> {
if (newValue != null) {
showChapterContent(newValue);
}
});
primaryStage.setScene(new Scene(root, 800, 600));
primaryStage.show();
}
public void searchNovelAndScrape(String novelName) {
Task<Void> task = new Task<Void>() {
@Override
protected Void call() throws Exception {
try {
setupChromeDriver();
driver.get('https://www.bige3.cc/');
WebElement searchInput = driver.findElement(By.xpath('/html/body/div[4]/div[1]/div[2]/form/input[1]'));
searchInput.sendKeys(novelName);
searchInput.sendKeys(Keys.ENTER);
WebDriverWait wait = new WebDriverWait(driver, 10);
wait.until(ExpectedConditions.presenceOfElementLocated(By.xpath('/html/body/div[5]/div/div/div/div/div[2]/h4/a')));
List<WebElement> elements = driver.findElements(By.xpath('/html/body/div[5]/div/div/div/div/div[2]/h4/a'));
if (elements.isEmpty()) {
showAlert('提示', '没有找到相关小说', Alert.AlertType.INFORMATION);
return null;
}
for (WebElement element : elements) {
element.click();
driver.switchTo().window(driver.getWindowHandles().stream().reduce((first, second) -> second).orElse(null));
wait.until(ExpectedConditions.presenceOfElementLocated(By.xpath('//*[@class='listmain']/dl/dd/a')));
List<WebElement> chapterElements = driver.findElements(By.xpath('//*[@class='listmain']/dl/dd/a'));
for (WebElement chapter : chapterElements) {
chapter.click();
while (true) {
wait.until(ExpectedConditions.presenceOfElementLocated(By.xpath('//*[@id='read']/div[5]/div[3]/h1')));
WebElement chapterTitleElement = driver.findElement(By.xpath('//*[@id='read']/div[5]/div[3]/h1'));
WebElement chapterContentElement = driver.findElement(By.xpath('//*[@id='chaptercontent']'));
String chapterTitle = chapterTitleElement.getText().replace('、', '');
String chapterContent = chapterContentElement.getAttribute('innerHTML')
.replaceAll('无弹窗,更新快,免费阅读!', '')
.replaceAll('请收藏本站:https://www.bige3.cc。笔趣阁手机版:https://m.bige3.cc', '')
.replaceAll('『点此报错』『加入书签』', '');
if (saveCheckBox.isSelected()) {
saveChapterToFile(novelName, chapterTitle, chapterContent);
}
String cleanContent = cleanChapterContent(chapterContent);
chapters.put(chapterTitle, cleanContent);
updateChapterList();
try {
wait.until(ExpectedConditions.elementToBeClickable(By.xpath('//div[@class='Readpage pagedown']/a[@id='pb_next']')));
WebElement nextButton = driver.findElement(By.xpath('//div[@class='Readpage pagedown']/a[@id='pb_next']'));
nextButton.click();
} catch (Exception e) {
break;
}
}
driver.navigate().back();
}
}
} catch (Exception e) {
e.printStackTrace();
showAlert('提示', '运行出错', Alert.AlertType.ERROR);
} finally {
cleanupChromeDriver();
}
return null;
}
};
Thread thread = new Thread(task);
thread.setDaemon(true);
thread.start();
}
private void setupChromeDriver() {
ChromeOptions options = new ChromeOptions();
options.addArguments('--no-sandbox');
options.addArguments('--disable-dev-shm-usage');
options.addArguments('--window-size=1920,1080');
options.addArguments('user-agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36'');
driver = new ChromeDriver(options);
}
private void cleanupChromeDriver() {
if (driver != null) {
driver.close();
driver.quit();
}
}
private void saveChapterToFile(String novelName, String chapterTitle, String chapterContent) {
String bookDir = System.getProperty('user.dir') + '/' + novelName;
File bookDirectory = new File(bookDir);
if (!bookDirectory.exists()) {
bookDirectory.mkdirs();
}
String chapterPath = bookDir + '/' + chapterTitle + '.txt';
try (BufferedWriter writer = new BufferedWriter(new FileWriter(chapterPath))) {
writer.write(chapterContent);
} catch (IOException e) {
e.printStackTrace();
}
}
private String cleanChapterContent(String chapterContent) {
Document doc = Jsoup.parse(chapterContent);
Elements chapterBrElements = doc.select('br');
return chapterBrElements.stream()
.map(brElement -> brElement.nextSibling().toString().trim())
.filter(text -> !text.isEmpty())
.collect(Collectors.joining('
')); }
public void updateChapterList() {
Platform.runLater(() -> {
chapterList.getItems().clear();
chapterList.getItems().addAll(chapters.keySet());
});
}
public void showChapterContent(String chapterTitle) {
Platform.runLater(() -> {
String content = chapters.get(chapterTitle);
if (content != null) {
content = content.replaceAll('<br>', '
'); // Replace
with spaces
chapterText.setText(content);
}
});
}
public static void showAlert(String title, String message, Alert.AlertType alertType) {
Platform.runLater(() -> {
Alert alert = new Alert(alertType);
alert.setTitle(title);
alert.setHeaderText(null);
alert.setContentText(message);
alert.showAndWait();
});
}
@Override
public void stop() {
cleanupChromeDriver();
}
原文地址: https://www.cveoy.top/t/topic/nJ1v 著作权归作者所有。请勿转载和采集!