.html -> Python Liste oder .xls

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
matheblauwal
User
Beiträge: 9
Registriert: Montag 4. März 2013, 19:54
Wohnort: 53639 Königswinter

Mein Ziel ist es eine Tabelle in einem html-Dokument in eine Excel-Datei umzuwandeln. Da ich aber nur Python als Programmiersprache kann will ich es in eine .csv-Datei umwandeln.
Damit ich eine .csv-Datei generieren kann, will ich das html-Dokument in ein mehrdimensionale Python-Liste konvertieren.

Code: Alles auswählen

01 <html>
02 	<body>
03 		<center>
04 			<table>
05 				<tbody>
06 					<tr>
07 						<td>
08 							<table>
09 								<tbody>
10 									<tr>
11 										<td>
12 										1****
13 										</td>
14 										<td>
15 										2****
16 										</td>
17 									</tr>
18 									<tr>
19 										<td>
20 										3****
21 										</td>
22 										<td>
23 										4****
24 										</td>
25 									</tr>
26 								</tbody>
27 							</table>
28 						</td>
29 						<td>
30 							<table>
31 								<tbody>
32 									<tr>
33 										<td>
34 											<font>
35 												5****
36 											</font>
37 										</td>
38									</tr>
39 								</tbody>
40 							</table>
41 						</td>
42 					</tr>
43 				</tbody>
44 			<table>
45 		</center>
46 	</body>
47 </html>
Dieser Code ist vereinfacht und strukturiert dargestellt (n**** steht für einen beliebigen String).
Im Prinzip soll die Liste so aussehen das für das für jede aufmachende Klammer (<...>) ein [ erstellt wird und für jede schließende Klammer (</...>) ein ]. Im Beispiel sollte die Liste so aussehen:

Code: Alles auswählen

[[[[[[[[[[[1****],[2****]],[[3****],[4****]]]]],[[[[[[5****]]]]]]]]]]]]
Noch besser wäre es wenn man alle <html>, <body> und <font> direkt ignoriert.

Danke für alle Antworten
Ich hoffe ich konnte mein Problem genau genug schildern, sodass jeder es versteht. Falls nicht versuche ich es dann noch einmal.


PS:
Über sowas wie

Code: Alles auswählen

import re

def einteilen(L, x):
	reihenfolge = ["html", "body", "center", "table", "tbody", "tr", "td", "table", "tbody", "tr", "td"]
	if x > len(reihenfolge)-1:
		return L
	Li = [einteilen(re.findall("<"+reihenfolge[x]+">.*</"+reihenfolge[x]+">"), x+1) for e in L]
	return Li

d = open("datei.html")
dt = d.read()
einteilen(dt, 0)
habe ich auch schon nachgedacht, doch dann könnte es passieren, dass der Bereich zwischen "<td>" in Zeile 11 und "</td>" in Zeile 37 und nicht der gewünschte Bereich (Z. 11 bis 13) gefunden wird. Auch re.findall("<td>.*?</td>", dt) hätte nicht den gewünschten Effekt, dort könnte dann z.B. der Bereich zwischen "<td>" in Zeile 7 und "</td>" in Zeile 3 gefunden werden.


PPS:
Vielleicht könnte man es mit einer Funktion versuchen die die aufmachenden (x += 1) und zumachenden (x -= 1) Klammern zählt, bis x wieder bei Null angekommen ist.
Real Programmers don't comment their code. It was hard to write, it should be hard to understand.
BlackJack

@matheblauwal: Vergiss am besten ganz schnell `re` für die Verarbeitung von HTML und verwende einen vernünftigen HTML-Parser. Zum Beispiel `lxml.html`. Dann ist das auch viel einfacher.

Edit: Vielleicht könntest Du auch mal mehr über das HTML verraten. Soll daraus *eine* HTML-Tabelle in CSV gewandelt werden? Denn bei der tief verschachtelten Listenstruktur die Du als Beispiel angibst, sehe ich nicht wie man die jetzt „natürlich” eine zweidimensionale CSV-Struktur umwandeln sollte. Es wäre vielleicht besser die Daten schon in der richtigen Form dafür aus dem HTML zu holen.

Ebenfalls interessant wäre ob das HTML wirklich *so* aussieht, oder ob es nicht noch Zusatzinformationen wie weitere Tags und Attribute gibt, an denen man sich bei der Extraktion von Werten orientieren kann.
matheblauwal
User
Beiträge: 9
Registriert: Montag 4. März 2013, 19:54
Wohnort: 53639 Königswinter

1. Was macht denn ein HTML-Parser? Könntest du mir vielleicht ein Code-Beispiel geben?
2. Also zu der Struktur des HTML-Dokumentes:
Das ganze ist ein Stundenplan. Die ganze Datei ist hier einzulesen: http://www.cjd-koenigswinter.eu/plaene/Kla1_8C.htm
Das ganze soll ein .csv-Dokument in etwa der Form werden:

Code: Alles auswählen

-;-----------;Montag;----;Dienstag
1;7:55 - 8:55;D-----;FIE-;IAD-----;NEL
-;-----------;------;B-35;--------;A-25
Die Bindestriche stehen für einen leeren String (sind also nur Platzhalter damit es nach einer Tabelle aussieht.)
Vielleicht wunderst du dich jetzt das am Dienstag nur ein Eintrag vorhanden ist und im HTML-Dokument sechs, aber das wird eine Eingabe sein, dass man spezifizieren muss welches Fach man bei welchem Lehrer hat.
Real Programmers don't comment their code. It was hard to write, it should be hard to understand.
BlackJack

@matheblauwal: Ein HTML-Parser überführt ein HTML-Dokument in einer Baumstruktur in der man dann zu den gewünschten Informationen navigieren kann. Dabei wird (von einem brauchbaren Parser) auch kaputtes HTML verarbeitet, wie es im Netz häufig vorkommt.

Der Stundenplan ist ja in ziemlich gruseligem HTML gespeichert. Tabellen in Tabellenzellen sind nicht gerade schön. Und bei Deinem CSV-Beispiel ist mir auch nicht so ganz klar nach welchen Regeln das erstellt werden soll. Pro Datenzelle hat man ja im HTML wieder eine Tabelle mit drei Spalten und teilweise mehreren Zeilen.

Edit: Ich würde empfehlen das Problem in kleinere Teilprobleme aufzuteilen, die dann durch jeweils eigene Funktionen gelöst werden. Ganz grob in Eingabe, Verarbeitung, und Ausgabe, beziehungsweise mindestens Ein- und Ausgabe zu trennen. Dafür das HTML-Dokument in eine Datenstruktur überführen, die alle relevanten Daten enthält, in einer Form die den Zugriff für die Verarbeitung/Ausgabe einfach macht. Das können Grunddatentypen (Listen, Wörterbücher, usw.) sein, aber vielleicht würden hier auch schon eigene Datentypen Sinn machen. Denn der Verschachtelungsgrad ist ja nicht sooo klein.

Und diese zwei oder drei Hauptprobleme dann wieder unterteilen in kleinere Teilprobleme bis diese einfach mit einer kurzen Funktion zu lösen sind. Aus den kleinen Funktionen dann die Gesamtlösung zusammen setzen.
matheblauwal
User
Beiträge: 9
Registriert: Montag 4. März 2013, 19:54
Wohnort: 53639 Königswinter

Ich dachte mir du könntest mir vielleicht einen Code anbieten um aus einem Stück HTML-Dokument und und einem string1 eine Liste aus strings zu erstellen die zwischen den passenenden Klammern (<string1 ...> bis </string1>) in der obersten Ebene des Stücks HTML-Code besteht.

Außerdem wäre es nett wenn du mir erklären könntest wie man lxml.html installiert (ich habe Windows XP und Python 3.1, es würde auch ein Link zu einer Seite reichen, die es auf Deutsch! erklärt)
Real Programmers don't comment their code. It was hard to write, it should be hard to understand.
BlackJack

@matheblauwal: Bei einem HTML-Parser denkt man bei der HTML-Struktur nicht mehr in Strings sondern in einem Objektbaum der aus den Elementen besteht. Text sind dann nur noch die Werte der Attribute und der tatsächliche Text der in/zwischen den Elementen steht. Beispiel zum finden der ersten Tabelle und deren Tabellenzeilen (<tr>-Elemente):

Code: Alles auswählen

In [140]: doc = html.parse('http://www.cjd-koenigswinter.eu/plaene/Kla1_8C.htm')

In [141]: doc.find('//table').findall('tr')
Out[141]: 
[<Element tr at 0xdaf132c>,
 <Element tr at 0xdaf135c>,
 <Element tr at 0xdaf138c>,
 <Element tr at 0xdaf13bc>,
 <Element tr at 0xdaf13ec>,
 <Element tr at 0xdaf141c>,
 <Element tr at 0xdaf144c>,
 <Element tr at 0xdaf147c>,
 <Element tr at 0xdaf14ac>,
 <Element tr at 0xdaf14dc>,
 <Element tr at 0xdaf150c>,
 <Element tr at 0xdaf153c>,
 <Element tr at 0xdaf156c>,
 <Element tr at 0xdaf159c>,
 <Element tr at 0xdaf15cc>,
 <Element tr at 0xdaf15fc>,
 <Element tr at 0xdaf162c>]
Die Elemente in der Liste sind dann wieder Objektbäume die den Inhalt der jeweiligen <tr>-Elemente enthalten und ihrerseits auch wieder über die ganzen Methoden zum finden und navigieren enthalten.

Windows-Installer für eine Menge Pakete gibt es hier http://www.lfd.uci.edu/~gohlke/pythonlibs/#lxml

Edit: Die Beispielseite in Python 2 in einer Datenstruktur überführt:

Code: Alles auswählen

#!/usr/bin/env python
# coding: utf-8
from itertools import imap, islice
from pprint import pprint
from lxml import html


def parse_cell(node):
    result = list()
    for row_node in node.findall('table/tr'):
        row = [n.text_content().strip() for n in row_node.findall('td')]
        if row != ['']:
            result.append(row)
    return result


def parse_row(node):
    cell_nodes = iter(node.findall('td'))
    start, end = next(cell_nodes).text_content().split()[1:]
    return {
        'timespan': {'start': start, 'end': end},
        'days': map(parse_cell, cell_nodes)
    }


def parse_table(node):
    # 
    # Kopfzeile und jede zweite Zeile des Rests (Leerzeilen) überspringen.
    # 
    return imap(parse_row, islice(node.findall('tr'), 1, None, 2))


def main():
    doc = html.parse('test.html')
    timetable = list(parse_table(doc.find('//table')))
    pprint(timetable)


if __name__ == '__main__':
    main()
Ob die Struktur der Zellen — verschachtelte Listen — so gut gewählt ist kann ich nicht sagen weil mir die Bedeutung der einzelnen Zellen in der Tabelle in jeder einzelnen Zelle nicht klar ist.

Ergebnis für die Beispielseite:

Code: Alles auswählen

[{'days': [[['D', 'FIE', 'B-35']],
           [['IAD.', 'SMI', 'K-17'],
            ['BTD', 'FAL', 'A-35'],
            ['BTD', 'STE', 'A-38'],
            ['PWD', 'HEL', 'A-24'],
            ['S8D', 'VRE', 'B-38'],
            ['IAD', 'NEL', 'A-25']],
           [['MU', 'PAN', 'K-14']],
           [['E5', 'PEI', 'A-K34']],
           [['IAD.', 'SMI', 'K-17'],
            ['IAD', 'NEL', 'A-26'],
            ['BTD', 'FAL', 'A-35'],
            ['BTD', 'NEH', 'A-37'],
            ['BTD', 'STE', 'A-38'],
            ['PWD', 'HEL', 'A-24'],
            ['S8D', 'VRE', 'B-38']]],
  'timespan': {'end': '8:55', 'start': '7:55'}},
 {'days': [[['F6.', 'PEI', 'A-K34'],
            ['F6', 'EIN', 'B-21'],
            ['F6', 'JAQ', 'A-K32'],
            ['L6', 'SPA', 'B-26'],
            ['L6', 'STN', 'B-F5'],
            ['L6', 'KAM', 'B-F4']],
           [['E5', 'PEI', 'A-K34']],
           [['BI', 'FAL', 'A-38']],
           [['F6.', 'PEI', 'A-K34'],
            ['F6', 'EIN', 'B-F3'],
            ['F6', 'JAQ', 'A-K32'],
            ['L6', 'SPA', 'B-26'],
            ['L6', 'STN', 'B-F5'],
            ['L6', 'KAM', 'B-F4']],
           [['M', u'K\xc4S', 'A-K25']]],
  'timespan': {'end': '10:10', 'start': '9:10'}},
 {'days': [[['KU', 'CHA', 'K-22']],
           [['F6.', 'PEI', 'A-K34'],
            ['F6', 'EIN', 'B-22'],
            ['F6', 'JAQ', 'A-K32'],
            ['L6', 'SPA', 'B-26'],
            ['L6', 'STN', 'B-F6'],
            ['L6', 'KAM', 'B-F4']],
           [['D', 'FIE', 'B-35']],
           [['M', u'K\xc4S', 'A-K25']],
           [['E5', 'PEI', 'A-K34']]],
  'timespan': {'end': '11:20', 'start': '10:20'}},
 {'days': [[['M', u'K\xc4S', 'A-24']],
           [['ER.', 'HEZ', 'B-02'],
            ['ER', 'RIT', 'B-37'],
            ['KR', 'BUB', 'B-35'],
            ['KR', 'KAR', 'B-24']],
           [['SP', 'LOR', 'SP1']],
           [['D', 'FIE', 'B-F7']],
           [['SP', 'LOR', 'SP2']]],
  'timespan': {'end': '12:45', 'start': '11:45'}},
 {'days': [[['PH', 'OSW', 'A-25']],
           [['CH', 'MEZ', 'A-31']],
           [['GE', 'STR', 'A-K24']],
           [['KU', 'CHA', 'K-22']],
           [['CH', 'MEZ', 'A-33']]],
  'timespan': {'end': '13:55', 'start': '12:55'}},
 {'days': [[], [], [], [], []],
  'timespan': {'end': '14:25', 'start': '13:55'}},
 {'days': [[['S8D.', 'VRE', 'B-38']], [], [], [['BTD.', 'NEH', 'A-34']], []],
  'timespan': {'end': '15:25', 'start': '14:25'}},
 {'days': [[], [], [], [], []],
  'timespan': {'end': '16:35', 'start': '15:35'}}]
matheblauwal
User
Beiträge: 9
Registriert: Montag 4. März 2013, 19:54
Wohnort: 53639 Königswinter

@BlackJack:
Danke vielmals das ist genau das, was ich suche mit so einer Liste kann ich jetzt arbeiten!!!
:D :D :D :D :D :D :D :D :D :D :D :D :D :D
Real Programmers don't comment their code. It was hard to write, it should be hard to understand.
Antworten