HTML und Regular Expressions

  • Antworten:16
Kurt G.
  • Forum-Beiträge: 67

22.03.2011, 11:42:36 via Website

Hallo liebe Community,

ich habe schon wieder mal ein Problem, und hoffe das ihr mir dabei helfen könnt...:grin::*)

Und zwar folgendes:

Ich versuche die Lottozahlen von Lotto.de zu nehmen und in Variablen zu packen....

habe im Internet diesen code gefunden... kann damit aber noch nich so viel anfangen:
1package app.mensa;
2
3import android.app.Activity;
4import android.view.*;
5import android.view.View.OnClickListener;
6import android.os.Bundle;
7import android.widget.Button;
8import android.widget.TextView;
9import java.net.*;
10import java.util.LinkedList;
11import java.util.ListIterator;
12import java.io.*;
13
14public class Mensa extends Activity implements OnClickListener{
15 private TextView twTest;
16 private TextView twCount;
17 private Button filterBtn;
18 private LinkedList<String> readHTML;
19 /** Called when the activity is first created. */
20 @Override
21 public void onCreate(Bundle savedInstanceState) {
22 super.onCreate(savedInstanceState);
23 setContentView(R.layout.main);
24 twTest = (TextView) findViewById(R.id.twTest);
25 twCount = (TextView) findViewById(R.id.TextView01);
26 filterBtn = (Button) findViewById(R.id.filterBtn);
27 filterBtn.setOnClickListener(this);
28 readHTML = new LinkedList<String>();
29 readMensaplan();
30 }
31
32 public void onClick(View v) {
33 switch (v.getId()) {
34 case R.id.filterBtn:
35 filterHTML();
36 }
37 }
38
39 public void filterHTML() {
40 ListIterator<String> it = readHTML.listIterator();
41 while(it.hasNext()) {
42 String s = (String) it.next();
43 if(!s.matches(".*(<td|<table|<tr|<th).*"))
44 it.remove();
45 else {
46 s = s.replaceAll("<[^<]+?>", "");
47 //s = hexToChar(s);
48 it.set(s);
49 }
50 }
51 twTest.setText(""+readHTML.size() + " " + readHTML.toString());
52 twCount.setText(""+readHTML.size() + " " + readHTML.get(5));
53 }
54
55 /*public String hexToChar(String input) {
56 input = input.replaceAll("&#228;", "ä");
57 input = input.replaceAll("–", "-");
58 input = input.replaceAll("&auml;", "ä");
59 input = input.replaceAll("&euro;", "€");
60 return input;
61 }*/
62
63 public void readMensaplan() {
64 try {
65 URL mensaPlanBs1 = new URL("http://www.sw-bs.de/braunschweig/essen/menus/mensa-1");
66 BufferedReader in = new BufferedReader(
67 new InputStreamReader(
68 mensaPlanBs1.openStream()));
69 String inputLine;
70 while ((inputLine = in.readLine()) != null) {
71 inputLine.trim();
72 readHTML.add(inputLine);
73 }
74 twTest.setText(""+readHTML.size());
75 in.close();
76 }
77 catch (IOException e) {
78 e.printStackTrace();
79 }
80 }
81}


Ich hoffe echt ihr könnt mir helfen, da das eigentlich das letzte Problem mit der App sein sollte....^^

Antworten
Rafael K.
  • Forum-Beiträge: 2.359

22.03.2011, 11:46:17 via Website

Naja da musst Du dir halt den Quelltext der Seite angucken und die Stellen begutachten, die du extrahieren willst.

Dann musst Du dir Regular Expressions basteln, die auf diese Stellen matchen.

Die Java Regex Doku findest du hier: http://download.oracle.com/javase/1.5.0/docs/api/java/util/regex/Pattern.html

Antworten
Kurt G.
  • Forum-Beiträge: 67

22.03.2011, 12:42:29 via Website

erstmal danke für den link, werds mir nachher mal in ruhe anschauen, aber hättest du evtl. auch ein zwei praxisbeispiele auf android zur hand?

Antworten
Rafael K.
  • Forum-Beiträge: 2.359

22.03.2011, 13:03:57 via Website

Unter Android ist das 100% genauso wie unter "normalem" Java.

Aus dem Kopf geht das ca. so:

1String input = "HTML QUELL TEXT";
2
3// matcht auf 3 Wörter mit Leerzeichen dazwischen,
4// die Klammern dienen zum Auslesen der gematchten Daten (matching groups)
5Pattern p = Pattern.compile("(\\w+) (\\w+) (\\w+)");
6Matcher m = p.matcher( input );
7
8if (m.find()) {
9String string1 = m.group(1); // = HTML
10String string2 = m.group(2); // = QUELL
11String string3 = m.group(3); // = TEXT
12}

— geändert am 22.03.2011, 13:05:10

Antworten
Frank W.
  • Forum-Beiträge: 5.103

22.03.2011, 15:46:01 via Website

Witzigerweise mache ich mit meinen aktuellen Krabbelversuchen auch sowas. Und ein Kollege hatte mich auch auf Lotto gebracht.

Da die mobile Lotto-Seite a) für die Infos vollkommen ausreicht und b) sehr einfach aufgebaut ist, wollte ich es gar nicht so "kompliziert" machen. Ich habe mir den Quelltext in einen String umgewandelt und kann dort nach belieben mit String-Funktionen umhersuchen.
Nicht die feine, englische Art, aber bei m.lotto.de mit m.E. weniger Aufwand als Regex zielführend.

Frank

"Irgendwann, möglicherweise aber auch nie, werde ich dich bitten, mir eine kleine Gefälligkeit zu erweisen." (Don Corleone) Für ein friedliches Miteinander"

Antworten
Rafael K.
  • Forum-Beiträge: 2.359

22.03.2011, 16:05:00 via Website

Frank W.
Nicht die feine, englische Art, aber bei m.lotto.de mit m.E. weniger Aufwand als Regex zielführend.

Frank

Dem möchte ich uneingeschränkt widersprechen :) (und das obwohl ich früher derselben Meinung war)
Informationen aus Strings würde ICH heute generell nur noch mit Regex auslesen.
Ist IMHO deutlich einfacher, wartbarer und weniger fehlerträchtig, als primitive String-Operationen.

Aufwand hat man nur einmal...nämlich wenn man versucht das Konzept zu durchdringen.
Danach fährt man mit Regex IMHO uneingeschränkt besser.
Der Einarbeitungsaufwand lohnt sich wirklich sehr !

In bestimmten Fällen tut es auch split() ... aber das arbeitet halt auch mit Regex :)
replaceAll() auch ... die Regex sind überall :)

— geändert am 22.03.2011, 16:14:19

Antworten
Frank W.
  • Forum-Beiträge: 5.103

22.03.2011, 16:13:05 via Website

OK! :)

Nein, wirklich! gut zu wissen. Dann werd ich mich dort mal einlesen. Als ich aufgehört habe zu programmieren, war das Internet noch uninteressant. Da waren auch "Interpretersprachen" total out und Java in den Kinderschuhen. :(

Frank

"Irgendwann, möglicherweise aber auch nie, werde ich dich bitten, mir eine kleine Gefälligkeit zu erweisen." (Don Corleone) Für ein friedliches Miteinander"

Antworten
Gelöschter Account
  • Forum-Beiträge: 294

22.03.2011, 16:27:52 via Website

Frank W.

Nicht die feine, englische Art, aber bei m.lotto.de mit m.E. weniger Aufwand als Regex zielführend.
Frank

Einspruch! Gerade auf einem Device wie einem Smartphone würde ich auf jeden Fall Pattern/Matcher nehmen. Das spart nicht unerheblich "Kraft".

Ich würde auch gerne darauf hinweisen das ich dann den compile() static/final deklarieren würde. Der compile() ist der aufwändigste Teil des regex und der sollte, wenn möglich, nur einmal kompiliert werden. Beispiel:

1private static final Pattern patternDuration = Pattern.compile("\"sumDuration\".+?\"display\".+?\"(.*?)\"", Pattern.DOTALL | Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);
2
3int start = 0;
4Matcher matcherDuration = patternDuration.matcher(content);
5if (matcherDuration.find(start)) {
6 foo.setBar(matcherDuration.group(1));
7}

Ich nehme in einer Android Anwendung von uns einen dicken String auseinander und hatte damals zu Beginn des Projektes die String Funktionen, die verschiedenen HTML/XML Parser mit Cleanern und Pattern/Matcher getestet. Pattern/Matcher war im Vergleich zu den anderen derart schnell und resourcenschonend das ich unter Android nie wieder andere Werkzeuge in Betracht gezogen habe. Natürlich kommt es immer noch ein wenig auf den konkreten Anwendungsfall an - aber bisher hat Pattern/Matcher bei mir immer (!!!) gewonnen.

Man muss sich nur an die teilweise modifizierte, jedenfalls im Gegensatz zu z.B. Perl, Syntax gewöhnen ... da gibt es kleine aber feine Unterschiede.

Gruß
Harald

— geändert am 22.03.2011, 16:29:35

Antworten
Rafael K.
  • Forum-Beiträge: 2.359

22.03.2011, 16:28:04 via Website

Kann ich echt nur empfehlen :)
Ich habs damals auch nur "gezwungenermaßen" im Studium gelernt, als es um endliche Automaten ging.
Seit ich die dann aber mal WIRKLICH verstanden habe, nutze ich die ständig.

Bei der Gelegenheit würde ich dann gern noch auf diesen Thread hinweisen ;-)
https://www.nextpit.de/de/android/forum/thread/418273/Speicherverbrauch-von-Strings-VS-16MB-RAM-Limit-pro-App

— geändert am 22.03.2011, 16:30:56

Antworten
Kurt G.
  • Forum-Beiträge: 67

23.03.2011, 13:26:56 via Website

So habe jetzt einige Zeit damit experimentiert, aber es will einfach nicht laufen....

hier mal der Quellcode:
1package de.code.worker.lotto.helferlein;
2
3import java.io.BufferedReader;
4import java.io.DataInputStream;
5import java.io.InputStreamReader;
6import java.net.URL;
7import java.net.URLConnection;
8import java.util.regex.Matcher;
9import java.util.regex.Pattern;
10
11import android.app.Activity;
12import android.os.Bundle;
13import android.widget.TextView;
14
15public class WelcomeScreenActivity extends Activity {
16
17 private TextView contentView;
18
19 /** Called when the activity is first created. */
20 @Override
21 public void onCreate(Bundle savedInstanceState) {
22 super.onCreate(savedInstanceState);
23 setContentView(R.layout.welcomescreenactivity);
24
25 contentView = (TextView)findViewById(R.id.twTest);
26
27 try {
28 URL url_ = new URL("http://m.lotto.de");
29 URLConnection conn = url_.openConnection();
30 DataInputStream in = new DataInputStream ( conn.getInputStream ( ) ) ;
31 BufferedReader d = new BufferedReader(new InputStreamReader(in, "utf-8"));
32 String content = "";
33 while(d.ready()) {
34 content += d.readLine();
35 }
36 String source = content;
37 Pattern p = Pattern.compile("(<span\b[^>]*>(.*?)</span>) (<span\b[^>]*>(.*?)</span>)");
38 Matcher m = p.matcher(source);
39 while (m.find()) {
40 String lottozahlen1 = m.group(1);
41 String lottozahlen2 = m.group(2);
42 contentView.setText(lottozahlen1 + lottozahlen2);
43 }
44
45 }
46 catch(Exception ex) {
47 contentView.setText("Exception:" + ex.toString() + " = " + ex.getMessage());
48 }
49
50 }
51}

Antworten
Rafael K.
  • Forum-Beiträge: 2.359

23.03.2011, 13:38:14 via Website

Kurt Gierke
So habe jetzt einige Zeit damit experimentiert, aber es will einfach nicht laufen....
Erinnert mich an nen Anruf bei ner Hotline: "Mein Computer geht nich!" :D

Ein paar mehr Details sind für Hilfestellung schon nötig ;-)


Mal ganz davon ab:
InputStreams nach Benutzung immer closen !!!

— geändert am 23.03.2011, 13:40:45

Antworten
Kurt G.
  • Forum-Beiträge: 67

23.03.2011, 13:55:52 via Website

:grin:

ja stimmt, also eigentlich soll er mir ja die ersten beiden lottozahlen(stecken zwischen <span></span>) anzeigen, jedoch zeigt er mir nichts an.... nehme ich jedoch die regular expression raus, dann ist der quellcode da....

Antworten
Rafael K.
  • Forum-Beiträge: 2.359

23.03.2011, 14:03:26 via Website

OK:

Vermutung 1:

\b ist in Strings ein reserviertes Zeichen und bedeutet Backspace
Wenn du ein \b im Regex Sinne meinst (also ein word boundary), musst du \\b schreiben.

Vermutung 2:

Du verschachtelst capturing groups. Dann solltest du genau drauf achten mit welchem Index du welche Gruppe bekommst.
Siehe dazu die oben verlinkte Doku und dort den Abschnitt mit dem ((A)(B(C))) Beispiel.

— geändert am 23.03.2011, 14:06:33

Antworten
Kurt G.
  • Forum-Beiträge: 67

23.03.2011, 14:21:20 via Website

also deine erste vermutung habe ich getestet, daran liegt es nicht, und bei deiner 2. vermutung, müsste du mir mal bitte erklären was genau du meinst
^^

EDIT: habe die Regular Expression auf der seite hier: http://gskinner.com/RegExr/ getestet, und da lief es einwandfrei....

— geändert am 23.03.2011, 14:23:45

Antworten
Rafael K.
  • Forum-Beiträge: 2.359

23.03.2011, 14:43:14 via Website

Das heisst nix. Java RegEx sind teilweise etwas anderes umgesetzt als z.B. in Perl.

Mach dir doch einfach ein kleines Programm mit einer main Methode,
speicher dir den HTML Schnipsel in einen String und experimentier dann rum.

Generell ist es aber so, dass du Backslashes escapen musst, also \\b ist korrekt.

Zu den capturing groups: Lies dir das Beispiel durch, dann verstehst du es schon :)

Ansonsten nimm doch einfach eine deutlich simplere Regex:

1<span[^>]*>([^<]+)</span>

Die matcht z.B. auf

1<span class="bla">Das ist ein beliebieger Text, der keine linke eckige Klammer enthält</span>

Antworten
Gelöschter Account
  • Forum-Beiträge: 294

23.03.2011, 15:10:59 via Website

Mach mal aus:
1Pattern p = Pattern.compile("(<span\b[^>]*>(.*?)</span>) (<span\b[^>]*>(.*?)</span>)");

das:
11. Pattern p = Pattern.compile("(<span.*?>(.+?)</span>) (<span.*?>(.+?)</span>)", Pattern.DOTALL | Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);

oder das:
12. Pattern p = Pattern.compile("<span.*?>(.+?)</span>", Pattern.DOTALL | Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);

Bei den beiden Gruppen in 1. bin ich mir nicht so sicher. Evtl. musst Du noch den Übergang "</span>) (<span.*?>" besser beschreiben. Derzeit hast Du dort ein Space stehen. Das könnte ja mal etwas anderes sein...

Zu 2.. Da ich die HTML-Seite nicht kenne: Mit dieser Variante "läufst." Du komplett durch alle <span>. Wenn es mehr <span> gibt mußt Du sowieso noch etwas als Erkennung drumherum bauen - auch in Deiner Ursprungsfassung und in meiner 1..

Der .*? hinter <span funktioniert auch wenn <span> alleine steht.

Der .+? bei den gesuchten Werten erwartet mindestens ein Zeichen. Wenn Du das nicht willst kannst Du dort auch .*? nehmen.

Ich habe die Erfahrung gemacht das ich im Java Regex häufig die "Reluctant quantifiers" (das sind die ?) nehmen musste obwohl meiner Meinung nach nicht gerade nötig. Diese erzeugen eine sehr umsichtige Suche und stoppen nach dem ersten Auftreten des nächsten Zeichens. Ich habe das dann aber nicht bis zu Ende ausprobiert sondern nehme sie jetzt halt immer.

Gruß
Harald

Antworten
Kurt G.
  • Forum-Beiträge: 67

23.03.2011, 21:30:20 via App

danke, habe jetzt eine andere lösung gefunden, werde denn quellcode dann morgen mal posten...

Antworten