我以前从未使用过ResourceBundle。
我有一个简单的Google刮取器(使用Jsoup库),我想定制并介绍多语言支持。
我有些课。首先,您必须让html文档稍后进行解析。
GooglePlayConnection类(提供连接,然后只需调用方法get()):
public class GooglePlayConnection {
/**
* Typical base part of URL for all apps.
*/
private static final String BASE_URL = "https://play.google.com/store/apps/details?";
private final String language;
private final String country;
public GooglePlayConnection(String language, String country) {
this.language = language;
this.country = country;
}
/**
* Tries to connect to provided URL. Uses localized version of URL that retrieves from {@link #getLocalized(Map, String, String)}.
* Also checks if URL applies to APPS category to prevent from parsing books/music/movies.
*/
public Connection connect(String URL) throws InvalidGooglePlayUrlException, MalformedURLException, URISyntaxException {
final java.net.URL url= new URL(URL);
if (GooglePlayCorrectURL.isUrlValid(url)) {
if (!url.getPath().contains("apps")) {
throw new InvalidGooglePlayUrlException("Wrong Google Play category");
}
Map params = getParameters(URL);
URL = getLocalized(params, language, country).toString();
return Jsoup.connect(URL);
}
else {
throw new InvalidGooglePlayUrlException("Not Google Play URL");
}
}
/**
* Gets localized version of provided URL.
*/
private URL getLocalized(Map params, String language, String country) throws MalformedURLException, URISyntaxException {
URIBuilder uriBuilder = new URIBuilder(BASE_URL)
.addParameter("id", params.get("id"))
.addParameter("hl", language)
.addParameter("gl", country);
return uriBuilder.build().toURL();
}
private Map getParameters(String url) throws MalformedURLException {
return Arrays.stream(new URL(url).getQuery().split("&"))
.map(s -> s.split("="))
.collect(Collectors.toMap(k -> k[0], v -> v.length > 1 ? v[1] : ""));
}刮刀:
public class GooglePlayAppsScraper {
/**
* Default language system settings to determine in which language to analyze HTML document.
*/
private static final ResourceBundle resourceBundle = ResourceBundle.getBundle("patterns", Locale.getDefault());
/**
* CSS-like element selectors that find elements matching a query.
*/
private static final String CURRENT_VERSION = resourceBundle.getString("game.currentVersion");
private static final String REQUIREMENTS = resourceBundle.getString("game.requirements");
public String getCurrentVersion(Document htmlDocument) {
return getIfAttributePresent(CURRENT_VERSION, htmlDocument);
}
public String getRequirements(Document htmlDocument) {
return getIfAttributePresent(REQUIREMENTS, htmlDocument);
}
...我有一些不同语言的包,像这样:
//en
game.currentVersion = div:matchesOwn(^Current Version$)
game.lastUpdate = div:matchesOwn(^Updated$)
game.installs = div:matchesOwn(^Installs$)
game.requirements = div:matchesOwn(^Requires Android$)
...//ru
game.currentVersion = div:matchesOwn(^Текущая версия$)
game.lastUpdate = div:matchesOwn(^Обновлено$)
game.installs = div:matchesOwn(^Количество установок$)
game.requirements = div:matchesOwn(^Требуемая версия Android$)
...重要的是什么?我需要把关于语言的信息“传送”到我的刮刀上。要搜索元素,文档中的语言必须与刮板将使用的语言匹配。
我不想把这些参数传递给每个刮板方法。它看起来将是:
public String getRequirements(Document htmlDocument, String language) {
ResourceBundle resourceBundle = ResourceBundle.getBundle("patterns", Locale.forLanguageTag(language));
String requirements = resourceBundle.getString("game.requirements");
return getIfAttributePresent(requirements, htmlDocument);
}我创建了LanguageSettings:
public class LanguageSettings {
private static Map settings = new HashMap<>();
public static Map getSettings() {
return settings;
}
public static void setSettings(Map settings) {
LanguageSettings.settings = settings;
}
}然后更新连接:
private final String language;
private final String country;
public GooglePlayConnection(String language, String country) {
this.language = language;
this.country = country;
LanguageSettings.getSettings().put("language", language);
}然后更新刮板:
private static final ResourceBundle resourceBundle = ResourceBundle.getBundle("patterns", Locale.forLanguageTag(LanguageSettings.getSettings().get("language")));简单用法:
String url = "https://play.google.com/store/apps/details?id=com.playdigious.cultist";
Document document = new GooglePlayConnection("en", "US").connect(url).get();
GooglePlayAppsScraper scraper = new GooglePlayAppsScraper();
System.out.println(scraper.getPrice(document));如果按正确的顺序执行(如示例中的那样),这将有效,但我确信会有更好的解决方案。
我应该如何连接我的刮刀和连接到一个ResourceBundle以适当的方式?
发布于 2020-09-01 13:00:33
我在这里看到的一个问题是:您的代码应该按正确的顺序调用才能正常工作。
这意味着您的组件有一个共享状态,它们没有预先状态( ResourceBundle和LanguageSettings),而且GooglePlayAppsScraper甚至是以这样的方式构建的,即以前的所有操作都必须在这个类加载之前消失--因为它静态地访问LanguageSettings并保存一个static final字段,当类从磁盘加载时,它的初始化将在应用程序生命周期中完成一次(不完全,但接近)。如果没有正确初始化,那么至少在NullPointerException中它会很快失败,但是我们可以做得更好。
我认为更好的方法是摆脱那里的静态状态。我建议让LanguageSettings成为整个系统的一个更明显的部分。
首先,让它成为一个对象:
public class LanguageSettings {
private String languageTag;
private String country;
public LanguageSettings(String language, String country) {
this.languageTag = Objects.requireNonNull(language, "languageTag");
this.country = Objects.requireNonNull(country, "country");
}
public String getLanguageTag() {
return languageTag;
}
public String getCountry() {
return country;
}
}然后,让GoogleConnection依赖它:
public class GoogleConnection {
private LanguageSettings settings;
public GoogleConnection(LanguageSettings settings) {
this.settings = Objects.requireNonNull(settings);
}
<...snip...>
private URL getLocalized(Map params) throws MalformedURLException, URISyntaxException {
URIBuilder uriBuilder = new URIBuilder(BASE_URL)
.addParameter("id", params.get("id"))
.addParameter("hl", settings.getLanguageTag())
.addParameter("gl", settings.getCountry());
return uriBuilder.build().toURL();
}
}同时,使GooglePlayAppsScraper依赖于同一个LanguageSettings对象进行初始化:
public class GooglePlayAppsScraper {
private ResourceBundle bundle;
/*
* Notice here: I am not reading from bundle, these are now simple constants
*/
private static final String CURRENT_VERSION = "game.currentVersion";
private static final String REQUIREMENTS = "game.requirements";
public GooglePlayAppsScraper(LanguageSettings settings) {
this.bundle = initBundle(settings);
}
private ResourceBundle initBundle(LanguageSettings settings) {
Objects.requireNonNull(settings);
return ResourceBundle.getBundle("patterns", Locale.forLanguageTag(settings.getLanguageTag());
}
public String getRequirements(Document htmlDocument) {
return getIfAttributePresent(bundle.getString(REQUIREMENTS), htmlDocument);
}
}这样做的好处--调用代码的“正确方式”现在基本上是唯一的方法--除非您已经有了LanguageSettings对象,否则就不能初始化GooglePlayAppsScraper或GoogleConnection。当您这样做时(假设您为两者提供了相同的对象),它将始终正确地初始化,因此很难交换两行代码并获得异常。您的应用程序现在还将支持并行运行多个连接和刮板(假设这是您想要的东西,外部服务允许它)-而且刮板程序甚至可以有不同的语言设置(因为Settings是每个应用程序的状态)。
https://codereview.stackexchange.com/questions/248508
复制相似问题