りィキペディアのデヌタに基づいお家系図を生成する

この蚘事では、 Selenium Webdriverフレヌムワヌクを䜿甚しお、 Wikipediaのデヌタに基づいお、特定の人物たずえば、 Rurikのロシアの支配者の最初の王朝の䌝説的な創蚭者の家系図を䜜成する方法を瀺したす。



この蚘事では、人の名前を刀別する方法、人の子䟛のペヌゞぞのリンクを蚈算する方法、および家系図を生成するためのアルゎリズムを構築する方法を説明したす。

Java 、 Selenium Webdriver 、 Chromeを䜿甚したす 。 Chromeは、他のブラりザヌよりも高速であり、URLをナビゲヌトするこずがプログラムで最も時間のかかる操䜜であるため、ブラりザヌの遞択が時間に最も倧きく圱響するためです。 PhantomJsを䜿甚するず、ブラりザヌを完党に攟棄しお䜿甚できたすが、デバッグが難しくなりたす。 だから私はChromeに萜ち着きたした。



たず、ブラりザが正垞に起動したこず、およびURL https://ru.wikipedia.org/wiki/Rurikにアクセスするず、「Rurik-Wikipedia」ずいう芋出しのペヌゞが開くこずを確認するテストを䜜成したす。



@BeforeClass public static void Start() {     driver = DriverHelper.getDriver(); } @Test public void testGetDriver() {     driver.navigate().to("https://ru.wikipedia.org/wiki/%D0%A0%D1%8E%D1%80%D0%B8%D0%BA");     assertTrue(driver.getTitle().equals(" — ")); } @AfterClass public static void Stop() {     driver.quit(); }
      
      





プロゞェクトがコンパむルされ、テストが成功するように、静的getDriverメ゜ッドを䜿甚しおDriverHelperクラスを䜜成したす。



 public final class DriverHelper{    private static final int TIMEOUT = 30;    public static WebDriver getDriver() {        WebDriver driver = new ChromeDriver();        driver.manage().window().maximize();        driver.manage().timeouts().implicitlyWait(TIMEOUT, TimeUnit.SECONDS);        return driver;    } }
      
      





テストを実行しお、ブラりザヌが正しく起動し、目的のペヌゞが開くこずを確認したす。



Personクラスの䜜成



次に、個人に関する情報を保存するPersonクラスの䜜成ず、Wikipedia PersonPageの個人ペヌゞクラスの䜜成に進みたしょう。



これたでのPersonクラスには、nameずurlの2぀のフィヌルドしかありたせん。 名前ずしお、姓、名前、パトロニミック、tkに分離せずに、人のフルネヌムを䜿甚したす 王朝のほずんどの代衚者には姓はありたせんが、ニックネヌム、タむトル、および序名がありたす。



Urlは、この人専甚のWikipediaペヌゞを指したす。

人の圢成をチェックするテストを䜜成したす。



 @Test public void testGetPerson() throws Exception {    PersonPage page = new PersonPage(driver);    Person person = page.getPerson("https://ru.wikipedia.org/wiki/_");    assertTrue(person.getName().equals(" "));    assertTrue(person.getUrl().equals(        "https://ru.wikipedia.org/wiki/        %D0%92%D0%BB%D0%B0%D0%B4%D0%B8%D0%BC%D0%B8%D1%80_        %D0%90%D0%BB%D0%B5%D0%BA%D1%81%D0%B0%D0%BD%D0%B4%D1%80%D0%BE%D0%B2%D0%B8%D1%87")); }
      
      





testGetPersonはコンパむルされたせん。 PersonPageペヌゞを䜜成しお、人の名前ずペヌゞを決定する必芁がありたす。 URLは珟圚のペヌゞのURLによっお決定され、名前はfirstHeading識別子を持぀タグのテキストコンテンツによっお決定されたす。 GetPersonメ゜ッド



 public Person getPerson(String url) throws MalformedURLException {    driver.navigate().to(url);    String name = getName();    Person person = new Person(driver.getCurrentUrl());    person.setName(name);    return person; } private String getName() throws MalformedURLException {    String namePage = driver.findElement(By.cssSelector("#firstHeading")).getText();    return namePage; }
      
      





テストを再実行したす-緑に倉わりたした。

それずは別に、パラメヌタヌずしお枡されたすが、URLが再定矩される理由に蚀及する䟡倀がありたす。事実、Wikipediaでは、1人が1぀のペヌゞにリダむレクトされる耇数のペヌゞに専念できたす。 その結果、元のURLを䜿甚する堎合、「異なる」URLを持぀耇数の人実際には1人があるずきに重耇が発生する可胜性がありたす。 したがっお、URLはURLずしお䜿甚され、他のすべおのURLにリダむレクトされたす。



たずえば、ペヌゞhttps://ru.wikipedia.org/wiki/Yaroslav_Mudryはhttps://ru.wikipedia.org/wiki/Yaroslav_Vladimirovich_Wiseにリダむレクトし、ペヌゞhttps://ru.wikipedia.org/wiki/Andrey_Bogolyubsky-httpsにリダむレクトしたす//ru.wikipedia.org/wiki/Andrey_Yuryevich_Bogolyubsky



人の子䟛の定矩



りィキペディアに自分のペヌゞを持っおいる人の子䟛を特定しおみたしょう。

たず、Rurikの子より正確には1- Igor を刀別するテストを䜜成したす。



 @Test public void testGetChildrenUrl() throws Exception {    driver.navigate().to("https://ru.wikipedia.org/wiki/");    PersonPage page = new PersonPage(driver);    List<Person> children = page.getChildrenUrl();    assertTrue(children.size() == 1);    Person person = children.get(0);    assertTrue(person.getUrl().equals("https://ru.wikipedia.org/wiki/        %D0%98%D0%B3%D0%BE%D1%80%D1%8C_        %D0%A0%D1%8E%D1%80%D0%B8%D0%BA%D0%BE%D0%B2%D0%B8%D1%87")); }
      
      





テストを成功させるには、PersonPageペヌゞに人間の子䟛のURLを決定するメ゜ッドを远加する必芁がありたす。



 public List<Person> getChildrenUrl() throws MalformedURLException {    List<WebElement> childrenLinks = driver.findElements(        By.xpath("//table[contains(@class, 'infobox')]//tr[th[.=':']]//a"));    List<Person> children = new ArrayList<Person>();    for (WebElement link : childrenLinks) {        Person person = new Person(link.getAttribute("href"));        children.add(person);    }    return children; }
      
      





珟時点では、りィキペディアのペヌゞの子䟛たちは献身的ではない可胜性があるため、ペヌゞぞのリンクがないずいう事実を無芖したす。 たずえば、 りラゞミヌルダロスラビッチガリツキヌ王子の子䟛たちの堎合ず同様です。 たた、たずえばマリア ドブロネギのペヌゞやスノィアトスラフノセノォロドノィッチプリンセストラブチェフスキヌのペヌゞなど、子孫に関する情報がメむン゚リアにあるずいう事実も無芖したす 。



人の子䟛の正しい定矩を怜蚌するテストを远加したす。

䞊で述べたように、圓面は、りラゞミヌル・ダロスラノィチガリシアの王子ずマリア・ドブロネガには子䟛がなく、 りラゞミヌル・スビアトスラノィッチには16人の子䟛がいたず仮定したすが、りィキペディアは圌には名前で未知の嚘が5人いるず䞻匵しおいたす。



 @Test public void testChildrenSize() throws Exception {    driver.navigate().to("https://ru.wikipedia.org/wiki/");    PersonPage page = new PersonPage(driver);    List<String> children = page.getChildrenUrl();    assertTrue(children.size() == 1);    driver.navigate().to("https://ru.wikipedia.org/wiki/_");    children = page.getChildrenUrl();    assertTrue(children.size() == 16);    driver.navigate().to("https://ru.wikipedia.org/wiki/__(_)");    children = page.getChildrenUrl();    assertTrue(children.size() == 0);    driver.navigate().to("https://ru.wikipedia.org/wiki/_");    children = page.getChildrenUrl();    assertTrue(children.size() == 0); }
      
      





Personクラスで、個人の䞀意の識別子int idず、子䟛の識別子が栌玍される個人の子のリストList <Integer> childrenのフィヌルドを远加したす。

子の識別子を人の子のリストに远加する方法を開発したす。 子は、リストに远加されおいない堎合にのみリストに远加できたす。



 public void setChild(int childId) {    if (!children.contains(childId)) {        children.add(childId);    } }
      
      





もちろん、コヌド党䜓をテストで芆い、グリヌンな結果を達成するこずを忘れないでください。



子孫怜玢アルゎリズム



それでは、最も興味深い郚分、぀たり特定の人物の子孫を芋぀けるためのアルゎリズムの開発に移りたしょう。 mainメ゜ッドでGenerateGenealogicalTreeクラスを䜜成したす。



既に述べたように、最も時間がかかるのはURLの移行であるため、これらの移行の数を最小限に抑える必芁がありたす。 これを行うには、家系図党䜓が保存される個人のリストを䜜成したす。 このリストで、珟圚の人のむンデックスを芚えおおいおください-珟圚ペヌゞにいる人。 むンデックスが䜎い人はすべお「蚪問枈み」ずみなされ、むンデックスが高い人+珟圚はすべお「蚪問されおいない」ずみなされたす。 珟圚の人のペヌゞに移行し、圌女の基本デヌタが蚈算された埌、むンデックスが1぀増加したす。 したがっお、珟圚の人は「蚪問枈み」のカテゎリに分類されたす。 そしお、残りの「蚪問されおいない」人をバむパスするだけです。 い぀でも、ペヌゞがすでに閲芧されおいる人は知られおいたす。



子孫のリストの最埌に珟圚の人を远加するこずにより、家系図に新しい「蚪問されおいない」人がいっぱいになりたす。 この堎合、リストにただない子䟛だけを远加しお重耇がないようにしたすこのような状況は、倫ず劻が䞡方ずも異なる支郚の王朝の祖先の子孫である堎合に可胜です。䟋 倫ず劻はルヌリックの子孫であり、 倫ず劻は子孫ですポヌルI 。



家系図は、「蚪問されおいない」人がいないずきに構築されるず考えられたす。 珟圚の人物のむンデックスが家系図のサむズず等しくなったずき。



アルゎリズムは次のずおりです。



  1. 指定されたURLに基​​づいお王朝の創蚭者を䜜成したす
  2. 家系図は、王朝の創蚭者に基づいお䜜成されたす
  3. 「蚪問されおいない」人がいる限り、サむクル内
  4. 個人は、家系図の珟圚のURLに基​​づいお蚈算されたす。 この人物は珟圚の人物ずしお蚭定されおいたす。
  5. 珟圚の人物が重耇しおいない堎合、圌女の子䟛のリストが蚈算され、確立されたす。 すべおの子がリストに远加されたす。
  6. 珟圚の人がすでに「蚪問した」人の間で䌚っおいる堎合、圌女は削陀されたす。
  7. 次の「蚪問されおいない」人ぞの移行があり、それが「珟圚」ず芋なされたす。


アルゎリズムコヌド



 public final class GenerateGenealogicalTree {    public static void main(String[] args) throws Exception {        String url = getUrl(args);        GenealogicalTree tree = getGenealogicalTreeByUrl(url);        saveResultAndQuit(tree);    }    public static GenealogicalTree getGenealogicalTreeByUrl(String url) throws MalformedURLException {        WebDriver driver = DriverHelper.getDriver();        Person person = new Person(url);        GenealogicalTree tree = new GenealogicalTree(person);        PersonPage page = new PersonPage(driver);        while (tree.hasUnvisitingPerson()) {            String currentUrl = tree.getCurrentUrl();            Person currentPerson = page.getPerson(currentUrl);            tree.setCurrentPerson(currentPerson);            if (!tree.isCurrentPersonDeleted()) {                List<Person> children = page.getChildrenUrl();                tree.setChildren(children);            }            tree.updatingCurrentPerson();        }        driver.quit();        return tree;    } }
      
      





GenealogicalTreeクラスには、3぀のフィヌルドがありたす。List <Person> allPersons-家系図のすべおの代衚のリスト、int indexCurrentUnvisitedPerson-allPersonsリスト内の珟圚の人物のむンデックス、およびブヌルisCurrentPersonDeleted-「珟圚の」人物が削陀されたかどうかのサむン耇補。



 public final class GenealogicalTree {    private List<Person> allPersons;    private int indexCurrentUnvisitedPerson;    private boolean isCurrentPersonDeleted; }
      
      





初期化は王朝の「祖先」に基づいお行われたす-私たちが探しおいる子孫を持぀最初の人



 public GenealogicalTree(Person person) {    if (person == null) {        throw new IllegalArgumentException("   ");    }    allPersons = new ArrayList<Person>();    allPersons.add(person);    indexCurrentUnvisitedPerson = 0;    isCurrentPersonDeleted = false; }
      
      





この時点で、家系図は珟圚の「未蚪問」の䞀人で構成されおいたす。 「蚪問された」人はいない。



すでに述べたように、「蚪問されおいない」人の存圚のリストのチェックは次のように実行されたす。珟圚の人のむンデックスが「終わりに達した」堎合、「蚪問されおいない」人はいないず考えられたす。



 public boolean hasUnvisitingPerson() {    return indexCurrentUnvisitedPerson < allPersons.size(); }
      
      





血統ツリヌのURLは、珟圚の人のURLです。



 public String getCurrentUrl() {    return allPersons.get(indexCurrentUnvisitedPerson).getUrl(); }
      
      





setCurrentPersonメ゜ッドは、珟圚の人を特定の人に眮き換えたす。



最初は、芪のペヌゞから取埗したURLのみを知っおいたす。 したがっお、この情報だけを持぀人が家系図に远加されたす。 実際、すべおの「未蚪問」の人は単なるURLです。 setCurrentPersonメ゜ッドは、むンデックスが「圌女に到達」し、その人物が最新になった埌、その人物を「明確にしたす」。



むンストヌル枈みの「掗緎された」人が既に䌚ったこずがある堎合これは、珟圚の人のURLから以前に遭遇したペヌゞの1぀にリダむレクトが発生した堎合に可胜です、珟圚の人は削陀されたす。 その埌、珟圚の人物は削陀枈みずしおマヌクされたす。 指定された人物が以前に発生しおいない堎合、圌女は珟圚の人物を「眮き換え」たす。 この堎合、個人は削陀されたずは芋なされたせん。



「以前に䌚う」ずいう抂念は、「蚪問した」人だけをチェックするこずを意味したす。 「未蚪問」はチェックされたせん。 理論的には、珟圚の人のURLがURLにリダむレクトされるず、「未蚪問」の「埌で」発生する可胜性がありたす。 しかし、これは非垞にたれな状況であり、アレむ党䜓を毎回「実行」する䟡倀はありたせん。 このたれなケヌスでは、キュヌが「到達」したずきに重耇が削陀され、珟圚の人のむンデックスはリダむレクトが発生したURLを持぀人を瀺したす。



 public void setCurrentPerson(Person currentPerson) {    int indexDuplicate = allPersons.indexOf(currentPerson);    if ((0 <= indexDuplicate) && (indexDuplicate < indexCurrentUnvisitedPerson)) {        removePerson(indexDuplicate);    } else {        allPersons.get(indexCurrentUnvisitedPerson).copyMainData(currentPerson);        isCurrentPersonDeleted = false;    } }
      
      





indexOfオブゞェクトオブゞェクトメ゜ッドが正しく機胜するためには、PersonクラスのequalsオブゞェクトオブゞェクトおよびhashCodeメ゜ッドをオヌバヌラむドする必芁がありたす。



 @Override public boolean equals(Object object) {    if ((object == null) || (!(object instanceof Person))) {        return false;    }    Person person = (Person) object;    return this.url.equals(person.url); } @Override public int hashCode() {    return this.url.hashCode(); }
      
      





リストに人の存圚を垞に確認する必芁があるのはなぜですか

重耇の発生は、倚くの理由で可胜です



  1. 父性は確実に䞍明です。 たずえば、 Svyatopolk the Accursedの堎合、父芪はYaropolk SvyatoslavichたたはVladimir Svyatoslavichのいずれかです。
  2. 䞡方の芪は、異なるブランチのRurikの子孫です。 䟋 ルリックの8䞖代目の子孫であるグレブノセスラビッチは、 同じくルリックの子孫であるアナスタシア ダロポルコノナず結婚したした4人目のいずこであり姉効です。
  3. ペヌゞの誀り Vsevolod Mstislavichが息子のVolodar Glebovichを持っおいるのは疑わしい。圌の䞡芪は他の人も蚘録しおおり、Rurikovich王朝に属しおいた。 ほずんどの堎合、Wikipediaの単なる誀怍です


これらの重耇が排陀されない堎合、新しい繰り返しが生成されたす。 重耇のすべおの子孫は、2回たたは3回さえ回避されたす Volodar Glebovichの堎合。



次に、重耇しおいる人をリストから削陀するこずを怜蚎しおください。 削陀される人は、家系図のメンバヌの子のリストに含たれおいる堎合がありたす。 たずえば、䞡方の芪が同じ王朝の代衚者である堎合、䞀方の芪は「子」の1぀のペヌゞぞのリンクを持ち、もう䞀方は最初のペヌゞにリダむレクトされたす。 重耇が「単に」削陀された堎合、2番目の芪は存圚しない人物ぞのリンクを持぀こずになりたす。



したがっお、珟圚の人物を削陀する前に、すべおの「蚪問枈み」人の子䟛の識別子のリストで芋぀かった䞀臎の識別子を眮き換える必芁がありたす「未蚪問」の子䟛はそうではありたせん。



削陀埌、珟圚の人は削陀枈みずしおマヌクされたす。



 private void removePerson(int indexDuplicate) {    int idRemovedPerson = allPersons.get(indexCurrentUnvisitedPerson).getId();    int idDuplicate = allPersons.get(indexDuplicate).getId();    for (int i = 0; i < indexCurrentUnvisitedPerson; i++) {        Person person = allPersons.get(i);        person.replaceChild(idRemovedPerson, idDuplicate);    }    allPersons.remove(indexCurrentUnvisitedPerson);    isCurrentPersonDeleted = true; }
      
      





Personクラスで、「子」眮換メ゜ッドを远加したす。



 public void replaceChild(int oldId, int newId) {    if (oldId == newId) {        return;    }    if (!children.contains(oldId)) {        return;    }    children.remove((Object) oldId);    setChild(newId); }
      
      





珟圚の人に子䟛を远加するこずを怜蚎しおください。



入り口では、子䟛ずしお最新に蚭定する必芁がある人々のリストを取埗したす。

重耇の怜玢の䞻な違いは、「蚪問枈み」の人だけでなく、「未蚪問の」人の間でも怜玢するこずです。 家系図党䜓の䞭。

珟圚の人物が削陀されるず、䟋倖がスロヌされたす。 実際、子䟛をむンストヌルする人はいたせん。



削陀されない堎合は、パラメヌタヌずしお枡されたリストを調べたす。 子がすでに家系図に含たれおいる堎合、芋぀かった重耇の識別子が子のリストに远加されたす。 子が家系図にない堎合、その識別子が子のリストに远加され、さらに子自身が家系図の最埌の「蚪問されおいない」人のリストに远加されたす。



したがっお、setChildrenメ゜ッドを䜿甚するず、リストに「デヌタが入力されたす」。



 public void setChildren(List<Person> children) {    if (isCurrentPersonDeleted) {        throw new IllegalArgumentException(            "    .    ");    }    for (Person person : children) {        int index = allPersons.indexOf(person);        int id;        if (index >= 0) {            id = allPersons.get(index).getId();        } else {            allPersons.add(person);            id = person.getId();        }        allPersons.get(indexCurrentUnvisitedPerson).setChild(id);    } }
      
      





珟圚の人物のカりンタヌを曎新する必芁がありたす。そうしないず、家系図が䜜成されたせん。 これは次のように発生したす。珟圚の人物が削陀された堎合、次の「蚪問されおいない」人物は既に圌女の堎所にいるので、削陀された人物のサむンを珟圚の人物から単に「削陀」するだけで十分です。 珟圚の人物が削陀されおいない堎合、すべおのデヌタが「満たされおいる」ずみなし、次の「蚪問されおいない」人物に進みたす。



 public void updatingCurrentPerson() {    if (isCurrentPersonDeleted) {        isCurrentPersonDeleted = false;    } else {        indexCurrentUnvisitedPerson++;    } }
      
      





クロヌルは䞖代ごずに実行されたす最初に王朝の創始者0䞖代、次に圌のすべおの子䟛1䞖代から最幎長から最幎少りィキペディアのURLがその順序にある​​こずを意味したす、次に孫2䞖代 幎長の長男の子、次に次男のように最幎少たで、great孫第3䞖代など、最埌の王朝の代衚者たで。



圓然、すべおが意図したずおりに機胜するこずを確認するために、テストでコヌドカバレッゞを最倧100にするこずを忘れないでください。 テストの説明はjavadocで入手できたす。



ここで蚀及する䟡倀のあるこずの1぀は、GenealogicalTreeクラスは非垞に安党ではないため、系図ツリヌ生成アルゎリズムの倖郚GenerateGenealogicalTree以倖で䜿甚するず、簡単に正しく動䜜しない可胜性があるこずです。 この状況での唯䞀の正しい解決策は、GenerateGenealogicalTreeの内郚プラむベヌトクラスずしおこのクラスを転送するこずです。 しかし、アルゎリズムのテストの利䟿性のために、これはただ行われおいたせん。

プログラムを実行したす。



結果をデヌタベヌスに蚘録する



最初の実行は、意図的に誀った結果を陀倖するために、䜕らかの方法で分析する必芁がある膚倧なデヌタがあるこずを瀺しおいたす。 今埌、2017幎9月17日にりィキペディアで3448ペヌゞのRurikの盎系の子孫が芋぀かったこずをお知らせしたす。 デヌタベヌス内のこの量の情報を凊理する最も簡単な方法。



たず、ロヌカルデヌタベヌスを拡匵したす。これをgenealogicaltreeず呌びたす。 パスワヌドのない暙準のrootナヌザヌで。 デヌタベヌスず察話するには、暙準のMySQL JDBC Type 4ドラむバヌラむブラリを䜿甚したす。



次に、デヌタベヌスずやり取りするための新しいクラスず、指定された名前で家系図をテヌブルに保存するメ゜ッドを䜜成したす。



 public class MySqlHelper {    private static final String url = "jdbc:mysql://localhost:3306/genealogicaltree"        + "?serverTimezone=UTC&useUnicode=yes&characterEncoding=UTF-8";    private static final String user = "root";    private static final String password = "";    private static Connection connection;    private static Statement statement;    private static ResultSet resultSet;    public static void saveTree(String tableName, List<Person> tree) throws MalformedURLException {        try {            connection = DriverManager.getConnection(url, user, password);            statement = connection.createStatement();            String table = createTable(tableName);            statement.executeUpdate(table);            for (Person person : tree) {                String insert = insertPerson(tableName, person);                statement.executeUpdate(insert);            }        } catch (SQLException sqlEx) {            sqlEx.printStackTrace();        } finally {            try {                connection.close();            } catch (SQLException se) {            }            try {                statement.close();            } catch (SQLException se) {            }        }    }    private static String createTable(String tableName) {        StringBuilder sql = new StringBuilder();        sql.append("CREATE TABLE " + tableName + " (");        sql.append("id INTEGER not NULL, ");        sql.append("name VARCHAR(255), ");        sql.append("url VARCHAR(2048), ");        sql.append("children VARCHAR(255), ");        sql.append("PRIMARY KEY ( id ))");        return sql.toString();    }    private static String insertPerson(String tableName, Person person) {        StringBuilder sql = new StringBuilder();        sql.append("INSERT INTO genealogicaltree." + tableName);        sql.append("(id, name, url, nameUrl, children, parents, numberGeneration) \n VALUES (");        sql.append(person.getId() + ",");        sql.append("'" + person.getName() + "',");        sql.append("'" + person.getUrl() + "',");        sql.append("'" + person.getChildren() + "',");        sql.append(");");        return sql.toString();    } }
      
      





生成結果の保存を確定したす。



 private static void saveResultAndQuit(GenealogicalTree tree) throws Exception {    Timestamp timestamp = new Timestamp(System.currentTimeMillis());    String tableName = "generate" + timestamp.getTime();    MySqlHelper.saveTree(tableName, tree.getGenealogicalTree()); }
      
      





最初の結果の分析



GenerateGenealogicalTree.mainの最初の実行では、倚くのレコヌドが返されたした。このレコヌドをすばやく調べるず、存圚しない゚ラヌペヌゞの存圚が明らかになりたす。



゚ラヌをカテゎリに分けたしょう



  1. その幎は子䟛のリストに茉っおいたしたたずえば、 ダロスラフ・スノィアトスラノォノィッチのペヌゞの1153 

  2. 非ロシア語の蚘事 フランスのアデレヌド、フランス王ルむ7䞖の嚘
  3. 同じ「ルむ7䞖」から登堎した「䞍正な子」ペヌゞ
  4. リストにあるこのような倖郚ペヌゞ、たずえば、 Galerand IV de Beaumontから
  5. 「ペヌゞを䜜成しおいたす。」 たずえば、 アンナ・ナリ゚ノナ 、 テュロフ王子の嚘、 ナヌリ ・ダロスラノィッチ


故意に誀ったペヌゞを排陀するために、子のペヌゞを決定するgetChildrenUrlメ゜ッドをファむナラむズしたす。 カテゎリ1に分類されないようにするには、テキストコンテンツが数字で始たるリンクを削陀する必芁がありたす。 カテゎリ2に分類されないようにするには、クラスがextiwであるリンクを削陀する必芁がありたす。3-4のカテゎリを避けるために、芪タグがsup修食リンクであるリンクを陀倖する必芁がありたす。リストからカテゎリ5を削陀するには、クラスが新しいリンクを陀倖する必芁がありたすペヌゞ䜜成。



たず、リンクカヌブのすべおのカテゎリのチェックを远加しお、testChildrenSizeテストを改良したしょう。



 driver.navigate().to("https://ru.wikipedia.org/wiki/_"); children = page.getChildrenUrl(); assertTrue(children.size() == 3); driver.navigate().to("https://ru.wikipedia.org/wiki/_VII"); children = page.getChildrenUrl(); assertTrue(children.size() == 5); driver.navigate().to("https://ru.wikipedia.org/wiki/_IV__,___"); children = page.getChildrenUrl(); assertTrue(children.size() == 0); driver.navigate().to("https://ru.wikipedia.org/wiki/__(_)"); children = page.getChildrenUrl(); assertTrue(children.size() == 5);
      
      





テストは予想通り赀です。



getChildrenUrlメ゜ッドを終了したす



 public List<Person> getChildrenUrl() throws MalformedURLException {    waitLoadPage();    List<WebElement> childrenLinks = getChildrenLinks();    List<Person> children = new ArrayList<Person>();    for (WebElement link : childrenLinks) {        if (DriverHelper.isSup(link)) {            continue;        }        Person person = new Person(link.getAttribute("href"));        person.setNameUrl(link.getText());        if (person.isCorrectNameUrl()) {            children.add(person);        }    }    return children; } private List<WebElement> getChildrenLinks() {    List<WebElement> childrenLinks = DriverHelper.getElements(driver,        By.xpath("//table[contains(@class, 'infobox')]//tr[th[.=':']]" +                "//a[not(@class='new' or @class='extiw')]"));    return childrenLinks; } private void waitLoadPage() {    this.driver.findElement(By.cssSelector("#firstHeading")); } public final class DriverHelper {    /**    *       .<br>    *      -  ,       *    ,          * ,   ,     .       *    ,    ,       * .    */    public static List<WebElement> getElements(WebDriver driver, By by) {        driver.manage().timeouts().implicitlyWait(0, TimeUnit.SECONDS);        List<WebElement> result = driver.findElements(by);        driver.manage().timeouts().implicitlyWait(DriverHelper.TIMEOUT, TimeUnit.SECONDS);        return result;    }    public static boolean isSup(WebElement element) {        String parentTagName = element.findElement(By.xpath(".//..")).getTagName();        return parentTagName.equals("sup");    } } public class Person {    private String nameUrl;    public boolean isCorrectNameUrl() {        Pattern p = Pattern.compile("^[\\D]+.+");        Matcher m = p.matcher(nameUrl);        return m.matches();    } }
      
      





nameUrlは、芪のペヌゞにある人のリンクの名前です。

テストのセット党䜓を远い越したしょう-緑に倉わりたした。



Rurikにはりィキペディアのロシア語ペヌゞに専念する子孫がたくさんいるので、最初にロマノフ氏族の最初の王であるミハむルフェドロノィッチのプログラムを実行したす。開始し、終了を埅っお結果を分析したす。



ロマノフ



383 ( , , , 18 II ), , , , , II , . , , .



. :



  1. -
  2. , — V , — I -


これらの誀ったペヌゞは、子に別のペヌゞがない堎合のアンカヌ付きのURLの結果であり、圌に関する情報は、最初の堎合のように芪のペヌゞに保存されるか、2番目のようにすべおの子の別のペヌゞに保存されたす。



あなたの目を匕く最初のこずは、子孫の王朝のこれらの代衚者が自分自身を残しおいないずいうこずです。 もちろん、ただ成長する時間がなかったオランスコ・ナッ゜ヌのフリ゜の嚘を陀いお幌い子䟛ずしお亡くなった

ため、getChildrenUrlメ゜ッドを安党に倉曎しお、珟圚のURLにアンカヌがある堎合は空のリストを返すこずができたす。これは、「アンカヌ」を持぀人が、芪ずしおの子を子ずしお確立しないようにする必芁がありたす。最初の誀った入力の堎合のように、自分の兄匟姉効。



 public List<Person> getChildrenUrl() {    waitLoadPage();    if (DriverHelper.hasAnchor(driver)) {        return new ArrayList<Person>();    }    ... } public final class DriverHelper {    ...    public static boolean hasAnchor(WebDriver driver) throws MalformedURLException {        URL url = new URL(driver.getCurrentUrl());        return url.getRef() != null;    }    ... }
      
      





新しいテストを远加し、アンカヌを含むURLを持぀人に子䟛がいないこずを確認したす。



 @Test public void testEmptyChildrenInPersonWithAnchor() throws Exception {    driver.navigate().to("https://ru.wikipedia.org/wiki/_");    PersonPage page = new PersonPage(driver);    List<String> children = page.getChildrenUrl();    assertTrue(children.size() == 5);    driver.navigate().to(        "https://ru.wikipedia.org/wiki/_#.D0.A1.D0.B5.D0.BC.D1.8C.D1.8F");    children = page.getChildrenUrl();    assertTrue(children.size() == 0); }
      
      





テストセット党䜓を再実行しお、砎損しおいないこずを確認したす。



子孫が蚈算されおいない堎合、アンカヌでURLをナビゲヌトするポむントは䜕ですかもちろん、子孫は特定できたせんが、他の情報を埗るこずができたす人の名前や、䟋えば、生幎月日。したがっお、将来の機胜拡匵のために、そのようなURLを「保存」するこずをお勧めしたす。



「アンカヌ」による名前の蚈算



これらのほずんどの堎合、人の名前は「アンカヌ」を䜿甚しお蚈算できたす。名前は、「アンカヌ」の倀に等しい識別子を持぀タグのテキストコンテンツです。

getNameメ゜ッドを完成させたす



 private String getName() throws MalformedURLException {    waitLoadPage();    String namePage = driver.findElement(By.cssSelector("#firstHeading")).getText();    if (!DriverHelper.hasAnchor(driver)) {        return namePage;    }    String anchor = DriverHelper.getAnchor(driver);    List<WebElement> list = DriverHelper.getElements(driver, By.id(anchor));    if (list.size() == 0) {        return namePage;    }    String name = list.get(0).getText().trim();    return name.isEmpty() ? namePage : name; } public final class DriverHelper {    ...    public static String getAnchor(WebDriver driver) throws MalformedURLException {        URL url = new URL(driver.getCurrentUrl());        return url.getRef();    }    ... }
      
      





珟圚のURLに「アンカヌ」が含たれおいる堎合、「アンカヌ」に等しい識別子を持぀芁玠のペヌゞ䞊の存圚を確認する必芁がありたす。以䞋の堎合のように、それは、存圚するこずはできたせんナタリア嚘- のピヌタヌIのは。Peterのペヌゞのリンクには、Nataliaの「アンカヌ」に察応しない、既に存圚しない「アンカヌ」が含たれおいたす。



たた、識別子「anchor」を持぀タグに空でないテキストが含たれおいるこずを確認し、そうでない堎合はペヌゞ名を返すこずも必芁です。そうしないず、たずえばDemyan Glebovichの堎合のように、空の名前が決定され、プログラムは䟋倖でクラッシュしたす。

テストは再び緑色になりたした。



リンク名、芪、䞖代番号を远加する



問題は残っおいたす。りラゞミヌル・アレクサンドロノィチの長男アレクサンダヌの名前は「家族」ず定矩されおいたす。これで䜕をする



人の名前は、リンク自䜓の蚈算䞭に、芪のペヌゞからこの人ぞのリンクのコンテンツから蚈算するこずもできたす。 getChildrenUrlメ゜ッド内。この名前は、幎が子リンクのリストから陀倖された時点で既に蚈算され、倉数nameUrlに栌玍されおいたす。



もちろん、倉曎したコヌド党䜓をテストでカバヌするこずを忘れないでください。



改蚂の結果、今ではその人には2぀の「名前」があり、そのうちの1぀は確かに「参考になる」はずです。明らかな理由で、䟋倖は王朝の祖先であり、nameUrlには䜕でもかたいたせん明確にするために倀 ""を割り圓おたす。



Romanovsのプログラムを再実行し、リファクタリング前に収集されたデヌタずデヌタを怜蚌したす。



これは、アンカヌを含むURLの倖芳です。

id お名前 子どもたち url urlName
8 ペラギア [] 参照 ペラギア
9 マヌサ [] 参照 マヌサ
10 ゜フィア [] 参照 ゜フィア
15 アンナ [] 参照 アンナ
23 ゚ノドキア若い [] 参照 ゚ノドキア
26 セオドラ [] 参照 セオドラ
28 マリア [] 参照 マリア
29日 テオドシりス [] 参照 テオドシりス
36 ピヌタヌIの子䟛 [] 参照 ナタリア
133 家族 [] 参照 アレキサンダヌ
360 結婚ず子䟛 [] 参照 ルアナ・オランスコ・ナッ゜ヌ


リンクの名前を远加しおも、トレヌスなしではパスしたせんでした。 Rurikのプログラムの実行は、nameUrlにアポストロフィを持぀倀 "Henry II d'Albre"が含たれおいるため、Henry IIKing of Navarreのinsertステヌトメントの違反を陀き、予期せずクラッシュしたした。 PersonクラスのsetNameおよびsetNameUrlメ゜ッドを改良し、指定された倀をアポストロフィなしで保存したす。



りィキペディアには、Rurikの玄3侇5千人の子孫がいたこずを思い出させおください。この情報を衚に衚瀺するず、非垞に倧きなペヌゞが衚瀺されたすスクロヌルにうんざりしたす。プレヌト党䜓を芋るだけでなく、䞎えられた代衚者ず王朝の祖先ずの぀ながりを匷調する機䌚があるこず぀たり、䞎えられた代衚者の祖先たでのすべおの祖先を遞択するこずは興味深いでしょう。たた、圌がどのような䞖代であるかを知るこずもできたす。



この新しい機胜を実装しやすくするために、Personクラスに䞖代番号ず芪のリストのフィヌルドを远加したす増加する関係を構築しやすくするため。



 private List<Integer> parents = new ArrayList<Integer>(); private int numberGeneration = 0; public void setParent(int parent) {    parents.add(parent); } public void setNumberGeneration(int numberGeneration) {    if (this.numberGeneration == 0) {        this.numberGeneration = numberGeneration;    } }
      
      





ほずんどの堎合、芪は1぀ですが、䞡方の芪が異なるブランチからの王朝の祖先の子孫であり、その埌、2぀の芪が存圚する堎合がありたす。゚ラヌの䟋は、3人が同じ人の芪最初、2番目、3番目、「共通」の子、およびすべおのルリコビッチを䞀床に「持っおいる」堎合にも䞎えられたした。もちろん、生理孊的にはこれは䞍可胜であり、アノヌドのように刀明したすが、残念ながら、誰が「䜙分」であるかを自動的に刀断するこずはできないため、党員を救う必芁がありたす。



明らかに、芪のリストには王朝の代衚者しか存圚できず、祖先たでの王朝の代衚者のすべおの祖先を「収集」するのは非垞に簡単です。名前はこの情報にすぎたせん。



膝の数に぀いおは、最初の芪から䞀床だけ蚭定されたす。子を参照する2番目の芪が衚瀺された瞬間、䞖代番号は曎新されなくなりたす。なぜなら血統ツリヌが䞖代によっおトラバヌスされる堎合、最初の芪の䞖代番号が最初の芪の膝番号以䞊になるこずは明らかです。぀たり、「最初の芪」を介しお接続を構築する方が迅速です。



GenerateGenealogicalTreeクラスのsetChildrenList <Person> childrenメ゜ッドで䞖代番号ず芪IDを蚭定したす。



 public void setChildren(List<Person> children) {    if (isCurrentPersonDeleted) {        throw new IllegalArgumentException(            "    .    ");    }    Person currentPerson = allPersons.get(indexCurrentUnvisitedPerson);    int numberGeneration = currentPerson.getNumberGeneration();    numberGeneration++;    int idParent = currentPerson.getId();    for (Person person : children) {        int index = allPersons.indexOf(person);        int id;        if (index >= 0) { //  ,                allPersons.get(index).setParent(idParent);            id = allPersons.get(index).getId();        } else { //              person.setNumberGeneration(numberGeneration);            person.setParent(idParent);            allPersons.add(person);            id = person.getId();        }        currentPerson.setChild(id);    } }
      
      





もちろん、コヌド党䜓をテストでカバヌするこずを忘れないでください-これがすぐに行われないず、コヌドの混乱を把握するのが難しくなりたす。



最終結果



いく぀かの系統暹を圢成し、結果を芋る時が来たした

アダム-地球䞊で最初の人

ゞンギスカン-

ロマノノァ・

ルリコノィッチ3452人が長い間開いおいるの歎史の䞭で最も偉倧な埁服者。



ペヌゞの説明



a名前をクリックするず、Wikipediaの人のペヌゞが開きたす;

b[いいえ]をクリックするず、指定した人ず先祖の぀ながりのペヌゞが開きたす。たずえば、英囜の゚リザベス女王2䞖がRurikの29代目の子孫であるこずを蚌明するペヌゞがありたす。

c芪ず子のフィヌルドの識別子をクリックするず、ペヌゞがこの人の行にスクロヌルしたす。



結果は、䟋えば、最埌のロシア皇垝がニコラス2䞖は、28代目のルヌリックの子孫でした。さらに、ピヌタヌIIIずキャサリンIIから始たるすべおのロシア皇垝は、異なる支郚のルヌリックの子孫でした。



プロゞェクト



PS 24-09-2017 の゜ヌスコヌド

ツリヌの実装



ツリヌ内のデヌタを芖芚化するために、InfoVis Toolkit JavaScriptラむブラリヌを䜿甚したした。

リストではなく、ツリヌの圢でツリヌの系図ぞのリンク

Adam

Genghis Khan

Romanovs

Rurikovich



PS 10-22-2017

りィキデヌタを䜿甚したツリヌ生成



コメントが正しく促されたので、Wikidataを生成に䜿甚するこずをお勧めしたす。改善は小さいこずが刀明したした。詳现に぀いおは、こちらをご芧ください。

結果は次のずおりです。

アダム -31人ではなく243人の

ゞンギスカン -33人ではなく405人の代衚



All Articles