Web-kaavinta e-kirjaksi BeautifulSoup ja Pandoc avulla

Original page: https://nullprogram.com/blog/2017/05/15/

Chris Wellons

15. toukokuuta 2017

Olen viime aikoina oppinut käyttämään BeautifulSoup, Python kirjasto manipulointiin HTML ja XML jäsennyspuuta, ja se on ollut fantastinen lisäksi minun virtuaalinen toolbelt. Aiemmin kun olen käsittelyyn tarvitaan raaka HTML, olen yrittänyt ikävä hakata Unix putket, tai reititys sisältöä web-selaimen kautta, jotta voisin manipuloida sitä kautta DOM API. Mikään niistä ei toiminut kovin hyvin, mutta nyt minulla on vihdoin BeautifulSoup täyttää tämä aukko. Sillä on valitsinkäyttöliittymä ja renderöintiä lukuun ottamatta se on käytännössä yhtä mukava HTML-muodossa kuin JavaScript.

Tämän päivän ongelma oli, että halusin lukea suositeltu verkossa kirjan nimeltä Haastatteleminen Nahka, tarina asetettu ”maailmassa, jossa caped sankarit taistelevat viheliäinen roistoja on heille arkipäivää.” Sanon ”online kirja”, koska 39403 sanaa tarina jaellaan sarja 14 blogikirjoitusta. En halua lukea sitä verkkosivuilla selaimessa, vaan mieluummin sitä e-kirjan muodossa, jossa se on mukavampaa. Viimeksi tein tämän, olen manuaalisesti kaavittu koko kirjan osaksi Markdown, vietti pari viikkoa sen muokkaamista virheitä, ja lopulta lähetti Markdown on Pandoc muuntaa e-kirja.

Tämän kirjan, haluan vain nopeasti ja likainen kaapia jotta siirtää muodoissa. En ole koskaan lukenut sitä, ja En ehkä edes pidä siitä (päivitys: nautin siitä), joten en todellakaan halua viettää paljon aikaa muuntaminen. Huolimatta hauskaa kirjoittamalla viime aikoina, olisin myös mieluummin pitää kaikki formating – kursiivilla jne – ilman palaamista kaiken käsin.

Onneksi Pandoc voi käyttää HTML: tä syötteenä, joten teoriassa voin syöttää sille alkuperäisen HTML: n ja säilyttää kaikki alkuperäiset merkinnät. Haasteena on, että HTML on levinnyt 14 sivulle, joita ympäröi kaikki odotetut blogin esteet. Tarvitsen jonkin verran tapaa purkaa kirjan sisältö jokaiselta sivulta, yhdistää se yhdessä lukujen otsikoiden kanssa ja lähettää tulos Pandocille. Kirjoita BeautifulSoup.

Ensinnäkin minun on rakennettava HTML-dokumentti. Sen sijaan, että koodin omaa HTML-koodia, aion rakentaa sen BeautifulSoupilla. Aloitan luomalla täysin tyhjän asiakirjan ja lisäämällä siihen doktyypin.

from bs4 import BeautifulSoup, Doctype

doc = BeautifulSoup()
doc.append(Doctype('html'))

Seuraavaksi luoda  html pääelementti, lisää sitten  head ja  body elementtejä. Olen myös lisätä  title elementti. Alkuperäisessä sisällössä on kuvitteelliset Unicoden merkinnät – vasemman ja oikean lainausmerkit, viiva jne, joten on tärkeää julistaa sivu UTF-8: ksi, koska muuten nämä merkit tulkitaan todennäköisesti väärin. Tuntuu aina omituiselta koodata koodattavan sisällön sisällä, mutta niin vain asiat ovat.

html = doc.new_tag('html', lang='en-US')
doc.append(html)
head = doc.new_tag('head')
html.append(head)
meta = doc.new_tag('meta', charset='utf-8')
head.append(meta)
title = doc.new_tag('title')
title.string = 'Interviewing Leather'
head.append(title)
body = doc.new_tag('body')
html.append(body)

Jos minä print(doc.prettify()) sitten näen haluamasi luuranko:

<!DOCTYPE html>
<html lang="en-US">
 <head>
  <meta charset="utf-8"/>
  <title>
   Interviewing Leather
  </title>
 </head>
 <body>
 </body>
</html>

Seuraavaksi olen koota luettelon yksittäisten blogikirjoitusta. Kun olin itse käsikirjoitusta, haluan ensin ladannut ne paikallisesti suosikkini ladata työkalun, kiemura, ja juoksi käsikirjoitus vastaan paikalliset kopiot. En halunnut lyödä web-palvelinta joka kerta testaamani. (Huomaa: Olen katkennut nämä URL-osoitteet sopimaan tähän artikkeliin.)

chapters = [
    "https://banter-latte.com/2007/06/26/...",
    "https://banter-latte.com/2007/07/03/...",
    "https://banter-latte.com/2007/07/10/...",
    "https://banter-latte.com/2007/07/17/...",
    "https://banter-latte.com/2007/07/24/...",
    "https://banter-latte.com/2007/07/31/...",
    "https://banter-latte.com/2007/08/07/...",
    "https://banter-latte.com/2007/08/14/...",
    "https://banter-latte.com/2007/08/21/...",
    "https://banter-latte.com/2007/08/28/...",
    "https://banter-latte.com/2007/09/04/...",
    "https://banter-latte.com/2007/09/20/...",
    "https://banter-latte.com/2007/09/25/...",
    "https://banter-latte.com/2007/10/02/..."
]

Käyn muutaman näitä sivuja selaimessa mitkä sivun osa haluan purkaa. Haluan katsoa riittävän tarkasti, mitä olen tekemässä, mutta ei liian tiiviisti, että se ei pilata itseäni! Napsauta selaimen sisältöä hiiren kakkospainikkeella ja valitsemalla ”Tarkasta elementti” (Firefox) tai “Tarkasta” (Chrome). ”Näytä sivulähde” toimisi myös, varsinkin kun tämä on staattista sisältöä, mutta minusta kehittäjäruutu on helpompi lukea. Lisäksi se piilottaa suurimman osan sisällöstä, paljastaen vain rakenteen.

Sisältö sisältyy div-luokkaan luokan sisääntulosisällön kanssa. Voin käyttää valitsinta erottaaksesi tämän elementin ja purkaen sen ala-p-elementit. Se ei kuitenkaan ole aivan niin yksinkertaista. Jokainen luku alkaa vähän kommentilla, jotka eivät kuulu kirjaan, enkä halua sisällyttää otteeseeni. Hr-elementti erottaa sen todellisesta sisällöstä. Toisen hr-elementin alapuolella on myös alatunniste, jonka todennäköisesti asetti sinne joku, joka ei ollut kiinnittänyt huomiota sivurakenteeseen. Se ei ole aivan loistava esimerkki semanttisesta merkinnästä, mutta se on riittävän säännöllinen, jotta voin hallita sitä.

<body>
  <main class="site-main">
    <div class="entry-body">
      <div class="entry-content">
        <p>A little intro.</p>
        <p>Some more intro.</p>
        <hr/>
        <p>Actual book content.</p>
        <p>More content.</p>
        <hr/>
        <p>Footer navigation junk.</p>
      </div>
    </div>
  </main>
</body>

Seuraava vaihe on vierailu kaikilla näillä sivuilla. Käytän enumerate koska haluan kappaleiden numerot lisättäessä h1 luvun elementit. Pandoc käyttää näitä sisällönluettelon laatimiseen.

for i, chapter in enumerate(chapters):
    # Construct h1 for the chapter
    header = doc.new_tag('h1')
    header.string = 'Chapter %d' % (i + 1,)
    body.append(header)

Seuraavaksi tartu sivun sisältöön käyttämällä urllib ja jäsentä se BeautifulSoupilla. Käytän valitsinta etsimään div kirjan sisällön kanssa.

    # Load chapter content
    with urllib.request.urlopen(chapter) as url:
        page = BeautifulSoup(url)
    content = page.select('.entry-content')[0]

Viimeinkin iteraa lapsen elementtejä div.entry-content elementti. Pidän juoksevan määrän hr v hr elementti.

    # Append content between hr elements
    hr_count = 0
    for child in content.children:
        if child.name == 'hr':
            hr_count += 1
        elif child.name == 'p' and hr_count == 1:
            child.attrs = {}
            if child.string == '#':
                body.append(doc.new_tag('hr'))
            else:
                body.append(child)

Jos se on p elementti, kopioin sen tulosteasiakirjaan, poistaen hetken kaikista p tag, koska jostain syystä joissakin näistä elementeistä on vanhanaikaisia kohdistusmääritteitä alkuperäisessä sisällössä.

Alkuperäinen sisältö käyttää myös tekstiä “#” itsessään a p erottaa osiot sen sijaan, että käytetään asianmukaista merkintää. Vaikka olenkin semanttisesti väärä, olen siitä kiitollinen siitä lähtien hr elementeillä olisi edelleen monimutkaisia asioita. Muundan nämä oikean merkinnän lopulliseen asiakirjaan.

Viimeinkin tulostan melko tuloksen:

print(doc.prettify())

Vaihtoehtoisesti voisin piipata sen läpi siisti.

$ python3 extract.py | tidy -indent -utf8 > output.html

Lyhyt tarkistus selaimen avulla osoittaa, että kaikki näyttää tulleen oikein. En kuitenkaan tiedä varmasti, ennen kuin olen itse lukenut koko kirjan. Viimeinkin saan Pandoc suorittamaan muuntamisen.

$ pandoc -t epub3 -o output.epub output.html

Ja siinä kaikki! Se on valmis lukemaan offline-tilassa valitsemassani e-kirjan lukijassa. Käsikirjoituksesi raa’an version kesti noin 15–20 minuuttia kirjoittamiseen ja testaamiseen, joten minulla oli e-kirjan muuntaminen alle 30 minuutissa. Se on noin niin kauan kuin olin valmis kuluttamaan saadakseni sen. Tämän artikkelin komentosarjan muokkaaminen kesti paljon kauemmin.

Minulla ei ole lupaa jakaa tuloksena olevaa e-kirjaa, mutta voin jakaa skriptini, jotta voit luoda oman, ainakin niin kauan kuin sitä ylläpidetään samassa paikassa samalla rakenteella.