Kuinka voin seuloa esimerkiksi tällaisesta html pätkästä...
<td class=esimerkki>
<center>
<b>abcdefghijklmn</b>
</center>
</td>
... tuon <b></b> sisällön käyttäen BS.
Tällä olen päässyt alkuun...
import urllib2
from BeautifulSoup import BeautifulSoup
url = 'http://www.esimerkki.com'
page = urllib2.urlopen(url)
soup = BeautifulSoup(page)
name = soup.findAll(attrs={'class' : 'esimerkki'})
Tuo siis tulostaa koko td sisällön, mutta kuinka jatkan, että saan tuon b tagien sisällön?
Koitin myös tällaista: print name.center.b , mut ei toiminut :)
En ole koskaan käyttänyt kyseistä kirjastoa, mutta toimiiko tämä? (linkki dokumentaatioon)
td = soup.find(attrs={'class' : 'esimerkki'}) print td.find('b').string
Edit: Ilmeisesti myös tuon name.center.b
pitäisi toimia. Ehkä ongelma oli, että käytit findAll
:ia find
:in sijasta? Kokeile tätä:
td = soup.find(attrs={'class' : 'esimerkki'}) print td.center.b.string print td.b.string
Ei tuokaan toimi. Voisko ongelma jotenkin liittyä siihen, että tuossa löydetään vain tuo yksi td (,jonka attribuutti on tuo esimerkki) elementti eikä sen sisälle voi enään viitata tuolla tavalla? Mene ja tiedä, kun muutenkin ensimmäistä kertaa tekemisissä pythonin kanssa :) Muita ehdotuksia?
Edit. Niin siis tuo ehdottamasi ratkaisu tuottaa seuraavan virheilmoituksen:
AttributeError: 'Nonetype' Object has no attribute 'center'
BeautifulSoupissa on sellainen ominaisuus, ettei se toimi kunnolla suoritettaessa IDLEn sisältä. Eli tallenna koodi tiedostoon ja aja se "python koodi.py" -komennolla. funktion neuvot olivat oikeita. Tässä vielä toimiva esimerkki:
from BeautifulSoup import BeautifulSoup a = """<td class=esimerkki> <center> <b>abcdefghijklmn</b> </center> </td>""" soup = BeautifulSoup(a) print soup.b.string print soup.find(attrs={'class' : 'esimerkki'}).b.string
Tulostaa:
abcdefghijklmn abcdefghijklmn
(koodipy-tagit eivät näköjään ymmärrä pythonin monirivistä """-merkkijonoa)
Noniin nythän lähti toimimaan. Kiitoksia paljon teille!
En viitti tehdä uutta viestiketjua niin pistän tähän perään kysymyksen. Eli saan tällaisen virheilmoituksen, kun koitan ajaa ohjelmaani:
UnicodeEncodeError: 'ascii' codec can't encode character u'\xe8' in position 27: ordinal not in range(128)
Kirjain on tämä 'è', jota koodekki ei osaa kääntää. Enkooderina käytän utf-8:a. Kuinka ratkaisen ongelman, että tuo kirjain tulkkautuisi oikein?
Oisiko dokumentista apua?
http://www.crummy.com/software/BeautifulSoup/
Cartter, jos ei onnistu, näytä koodista oleelliset kohdat. Eli mistä virheen antava merkkijono luetaan (millä merkistöllä se on), muunnatko sen unicodeksi (unicode-funktiolla, parametrina oikea merkistö) ja tulostatko sen merkkijonon encode-metodin avulla?
Noniin :D Pitänee vissiin lopettaa tältä päivältä, kun ei näköjään silmät enään pelitä. Kiitoksia kayttaja-2499!
Edit. Kiitoksia Chiman. Sain kuitenkin ratkaistua ongelmani.
Viel tulis yksi kysymys:
Mitä tässä tapahtuu:
Traceback (most recent call last):
File "parser.py", line 22, in <module>
soup = BeautifulSoup(html)
File "C:\Python25\lib\BeautifulSoup.py", line 1470, in __init__
BeautifulStoneSoup.__init__(self, *args, **kwargs)
File "C:\Python25\lib\BeautifulSoup.py", line 1085, in __init__
markup = markup.read()
File "C:\Python25\lib\socket.py", line 291, in read
data = self._sock.recv(recv_size)
File "C:\Python25\lib\httplib.py", line 509, in read
return self._read_chunked(amt)
File "C:\Python25\lib\httplib.py", line 563, in _read_chunked
value += self._safe_read(chunk_left)
File "C:\Python25\lib\httplib.py", line 604, in _safe_read
raise IncompleteRead(s)
httplib.IncompleteRead: ['on><option value="esimerkki.php?action=category&id=18">a</
Selvätikkään jotain ei saada luettua. Mistä tämä virhe voi johtua?
Käynyt kyllä jo tässä vaiheessa selväksi, että kun tämän tapaisia ongelmia tulee niin netti tarjoaa aika heikot eväät ratkaisun löytämiseksi edes englanniksi.
En osaa antaa suoraa neuvoa, mutta jos laitat johonkin pastebiniin sekä koodin että ongelmallisen html:n, niin minä tai joku muu voi itse kokeilla.
Ihan kuin lähdekoodi loppuisi kesken? Hakuammuntaa: Lue koko sivu muuttujaan näin:
page = urllib2.urlopen(url).read()
Sinulta puuttui tuo .read() ainakin aiemmasta koodipätkästä.
Selvä. Koodi on kyllä niin lyhyt, että vissiin olisi tähänkin voinut laittaa, mutta pistin nyt kuitenkin pastebiniin.
Tuo read() ei auttanut asiaa eikä itseasiassa muuttanut ohjelman toimintaa mitenkään. Muutin kyllä hieman tuota sivun avaamista, jotta sain tuon user-agent-headerin lisättyä.
Olisi mukava, jos joku viitsisi samalla kommentoida hieman tuota koodia ja antaa mahdollisia parannusehdotuksia. Se ei varmasti ole suorin tapa asian suorittamiseen, mutta kahden viikon python kokemuksella en parempaan pystynyt ja ainakin ohjelma toimii niinkun olin suunnitellut.
Testasin pikaisesti. Näytti heittävän StandardErrorin tyhjillä sivuilla tällä rivillä:
name = soup.find(attrs={'class' : 'headercell'}).b.string
Pilko sitä vähän. Tutki löytyykö tuota headercelliä ja vasta jos löytyy, nappaa sieltä b.string. Tai vaihtoehtoisesti rajaa try-lohko vain tuohon riviin ja laita continue-käsky poikkeuksen käsittelyyn.
Yleisesti ottaen koodi näyttää oikein siistiltä ja selkeältä.
q-while-silmukan voisit korvata for q in range(1, 501):-silmukalla. Vältyt erikseen q:n alkuarvon asettamiselta ja muuttujan kasvattamiselta.
re.compile on tarkoitettu nopeuttamaan toistuvia samoja regexp-hakuja. Tässä niitä toistoja tulee, joten voisit tehdä re.compilen silmukan ulkopuolella ja käyttää silmukan sisällä re.searchiä näin saadulla objektilla. Tämä ei ole nopeuskriittinen sovellus, mutta tuo on yleinen periaate. (Kertakäytöt toteutetaan ilman re.compilea laittamalla regexp-lauseke suoraan searchille.)
Sleepin käyttö oli hyvä, jottei sivustoa kuormiteta olan takaa.
a[len(a)-1] => a[-1]
Poikkeusten käytössä try-lohko on usein järkevää rajata mahdollisimman pieneksi.
Leffan nimen loppuun näytti jäävän välilyöntejä. nimi.strip() auttaa.
Käyttötarkoituksesta riippuen datan tallennukseen voi harkita myös pickle- ja sqlite3-moduuleja.
http://docs.python.org/lib/module-pickle.html
http://docs.python.org/lib/module-sqlite3.html
Muoks. shelve on yleensä pickleä kätevämpi:
http://docs.python.org/lib/module-shelve.html
Tietenkin myös lxml:n ElementTree on tutustumisen arvoinen, koska sen kanssa voi käyttää XPath:iä:
from lxml import etree doc = etree.fromstring(f) for polygon in doc.xpath("/level/polygons/polygon"): print polygon.xpath("id/text()")
Ja XPathin documentaatio: http://www.w3.org/TR/xpath
Kiitoksia vastauksista.
Chiman kirjoitti:
re.compile on tarkoitettu nopeuttamaan toistuvia samoja regexp-hakuja. Tässä niitä toistoja tulee, joten voisit tehdä re.compilen silmukan ulkopuolella ja käyttää silmukan sisällä re.searchiä näin saadulla objektilla. Tämä ei ole nopeuskriittinen sovellus, mutta tuo on yleinen periaate. (Kertakäytöt toteutetaan ilman re.compilea laittamalla regexp-lauseke suoraan searchille.)
Tarkoitatko, että tekisin näin:
digitpattern = re.compile(r'\d{4}') <- silmukan ulkopuolelle re.search (digitpattern, year)
tämän vanhan sijasta:
re.search (re.compile(r'\d{4}'), year)
Joo täytyypä perehtyä tuohon datan tallenukseen tässä joku päivä syvemmin.
Kiitoksia avusta.
P.S tuo sleeptime tulee olemaan kyllä suurempi. Tuo on vain testaamista varten.
Edit.
Voiko näitä rivejä korvata jollain kompaktimmalla:
name = name.replace(year, "") name = name.replace("(", "") name = name.replace(")", "")
eli ts. voiko noita korvattavia merkkejä yhdistää jotenkin samaan replace-funktioon?
Edit2.
Nuo suosittelemasi toimenpiteet eivät auttaneet asiaa sen suhteen, että edelleen saan tuon saman virheilmoituksen tuolla samaisella sivulla. Mahtaako tuossa sivussa sitten olla jotain poikkeuksellista muihin sivuihin nähden, jotka saan luettua tekstitiedostoon.
Cartter
Cartter kirjoitti:
Tarkoitatko, että tekisin näin: ...
Kyllä, juuri noin.
Cartter kirjoitti:
Voiko näitä rivejä korvata jollain kompaktimmalla:
name = name.replace(year, "") name = name.replace("(", "") name = name.replace(")", "")eli ts. voiko noita korvattavia merkkejä yhdistää jotenkin samaan replace-funktioon?
Ei ainakaan tuohon, enkä muista muutakaan vastaavaa funktiota. Tekisin itse periaatteessa näin:
for s in (year, '(', ')'): name = name.replace(s, '')
...mutta koodia voisi yksinkertaistaa huomattavasti laatimalla yksittäinen regexp, joka poimii sekä leffan nimen että vuoden. Silloin saat heti nimen ja vuoden puhtaina talteen.
Cartter kirjoitti:
Nuo suosittelemasi toimenpiteet eivät auttaneet asiaa sen suhteen, että edelleen saan tuon saman virheilmoituksen tuolla samaisella sivulla. Mahtaako tuossa sivussa sitten olla jotain poikkeuksellista muihin sivuihin nähden, jotka saan luettua tekstitiedostoon.
Vika näytti tulevan mm. Ringu-leffassa (id: 106), joka sisälsi ei-ascii-merkin. Poikkeus tuli riviltä, jonka korjasin tällaiseksi:
fileHandle.write(info.encode('utf8'))
Eli BeautifulSoup muistaakseni palauttaa aina unicode-merkkijonoja. Unicodella ei ole "fyysistä olomuotoa", vaan unicode-merkkijono pitää aina tulostettaessa muuntaa johonkin merkistöön. Oletuksena on ascii, ja kun vastaan tulee merkki, jota asciilla ei voi esittää, tulee virhe (tässä tapauksessa "UnicodeEncodeError: 'ascii' codec can't encode character u'\xe2' in position 10: ordinal not in range(128)"). Saatat haluta käyttää latin1:ä utf8:n sijaan.
Joo tuo UnicodeEncodeError oli ongelma alkuvaiheessa, mutta pääsin siitä eroon tekemällä pythonin hakemistoon sitecustomize.py tiedoston, johon tällainen pätkä koodia:
import sys sys.setdefaultencoding("utf-8")
Eli tuota UnicodeEncodeError:a ei ole sen jälkeen tullut.
Ongelma on id: 157 eli Final Destination 2.
Traceback (most recent call last):
File "parser.py", line 20, in <module>
html = opener.open(url).read()
File "C:\Python25\lib\socket.py", line 291, in read
data = self._sock.recv(recv_size)
File "C:\Python25\lib\httplib.py", line 509, in read
return self._read_chunked(amt)
File "C:\Python25\lib\httplib.py", line 563, in _read_chunked
value += self._safe_read(chunk_left)
File "C:\Python25\lib\httplib.py", line 604, in _safe_read
raise IncompleteRead(s)
httplib.IncompleteRead: ['on><option value="subiarkisto.php?action=category&id=18">R</option><option value="subiarkisto.php?action=category&id=19">S</option><option value="subiarkisto.php?action=category&id=20">T</option> [...]
Cartter kirjoitti:
Ongelma on id: 157 eli Final Destination 2.
Minulla toimii oikein: http://pastebin.com/fe83f5f2
Versiotiedot:
Python 2.5.2 (r252:60911, Jul 31 2008, 17:28:52)
BS: __version__ = "3.0.7a"
Ubuntu 8.04
Pitihän sitä vähän siistiä vielä:
# -*- coding: utf-8 -*- import urllib2 import time import re from BeautifulSoup import BeautifulSoup fileHandle = open('testdb.txt', 'a') opener = urllib2.build_opener() opener.addheaders = [('User-agent', 'Mozilla/5.0')] re_movie = re.compile(r'^\s*(?P<name>.+?)\s*(?P<year>\(\d{4}\))?\s*$') for q in range(25, 200): time.sleep(1) url = 'http://www.subiarkisto.org/subiarkisto.php?action=file&id=%d' % q html = opener.open(url).read() soup = BeautifulSoup(html) movie_class = soup.find(attrs={'class': 'headercell'}) if movie_class == None: continue movie = re.match(re_movie, movie_class.b.string).groupdict() if movie['year']: year = movie['year'][1:-1] # remove parentheses else: year = 'no_year_available' # link to subtitles link = 'http://www.subiarkisto.org/' + soup.find(attrs={'class': 'head'})['href'] info = '%s|%s|%s\n' % (movie['name'], year, link) fileHandle.write(info.encode('utf8')) fileHandle.close()
Hmm... itselläni on sama version (2.5.2) pythonista ja sama versio BS:sta. Ainoa ero on se, että koneellani on winxp pro. Ja minulla tuo ei toimi kun 157 id asti.
Pitänee asennella linux tuohon vanhempaan koneeseen ja testailla sillä.
Kiitos avusta Chiman. Täytyy tarkastella lähemmin tuota pasteemaasi koodia.
Googlasin jotain pythoniin liittyvää ja tämä viestiketju oli ensimmäinen hakutulos. Olis vissiin aika korjata nuo muutamat virheet tuolta. Eli millä vaihdan viestiketjun otsikkoa? Toinen kysymys, miksei noita aikaisempia viestejä voi muokata?
Aihe on jo aika vanha, joten et voi enää vastata siihen.