Seite 1 von 1
Extraktion von Links aus der Skype-History
Verfasst: Sonntag 13. Mai 2012, 19:12
von webspider
Wie ihr vielleicht wisst, ist der Zugriff auf die Skype-History sehr behäbig und scheint sogar Speicherlecks zu verursachen. Daher habe ich ein wenig nachgeforscht und mir ein Skript erstellt, welches für mich sämtliche Links extrahiert. Die entstandene Liste könnte man dann zum Beispiel so weiterverarbeiten:
Code: Alles auswählen
grep -E "png|jpg|gif" skype_links.txt > image_list.txt
Code: Alles auswählen
#! /usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import print_function
import codecs, sqlite3, re
# replace with the path to main.db, the database containing your skype history
HISTORY_FILE = "/Users/username/Library/Application Support/Skype/username/main.db"
OUTPUT_FILE = "skype_links.txt"
SQL_STATEMENT = "SELECT body_xml FROM Messages"
PATTERN = re.compile("<a href=\"(?P<url>.*?)\">(?P<text>.*?)</a>")
def main():
links = set() # if you use a set instead of a list you don't need to remove duplicates
connection = sqlite3.connect(HISTORY_FILE)
c = connection.cursor()
for message in c.execute(SQL_STATEMENT):
# for some reason simply selecting the message body doesn't suffice
# so I check whether the first element of it exists at all
if message[0]:
# I need to use a loop because I don't know how many URLs the message contains
for match in re.finditer(PATTERN, message[0]):
links.add(match.group("url"))
# this is needed to make sure it works with python 2.x and 3.x
with codecs.open(OUTPUT_FILE, "w", "utf-8") as output_file:
output_file.write("\n".join(sorted(links)))
print("{} links were written".format(len(links)))
if __name__ == "__main__":
main()
Evtl. baue ich später noch Unterstützung von Kommandozeilenparametern durch argparse ein (Eingabedatei, Ausgabedatei, Ausgabeformat). Sollten andere beschämende Probleme vorliegen, sagt ruhig Bescheid.
Re: Extraktion von Links aus der Skype-History
Verfasst: Sonntag 13. Mai 2012, 19:54
von BlackJack
@webspider: Du müsstest eigentlich lange genug hier mitlesen um zu wissen, dass `re` und XML zwangsläufig zu einem Hinweis auf XML-Parser führen wird.
Der Kommentar über dem ``if message[0]`` ist falsch. Damit prüfst Du nicht ob das Element existiert, sondern ob dessen Wert im Boole'schen Kontext wahr ist. Dazu muss es zwingend existieren! Würde es das nicht, bekämst Du an der Stelle für den Test einen `IndexError` um die Ohren gehauen. Ich nehme einfach mal an, dass nicht jede Message ein `body_xml` hat, und da Nullwerte in der Tabelle vorkommen dürfen‽ Dann bekommt man `None` als Wert.
Die Datenbankverbindung wird nicht wieder geschlossen. Und ich hätte `cursor` ausgeschrieben.
Re: Extraktion von Links aus der Skype-History
Verfasst: Sonntag 13. Mai 2012, 20:11
von webspider
Ich würde ja auch einen XML-Parser einsetzen, aber doch nicht wenn die Nachrichten nicht einmal korrektes XML sind, sondern in der überwiegenden Mehrheit der Fälle lediglich kurzer Text mit einem Anker oder vielleicht noch Smiley drin. Bei solchen Fällen kommt mir der Einsatz von lxml wie mit der Kanone auf Spatzen geschossen vor
Die Datenbankresultate sind ein wenig merkwürdig. Wenn die Nachricht leer sein soll, erhalte ich ``(None,)``als Rückgabewert statt einem einfachem None, andernfalls entspricht der erste Wert des Tupels dem Nachrichteninhalt. Dennoch hast du Recht, dass mein Kommentar diesbezüglich verwirrender ist als er hilft. Den Rest korrigiere ich mal schnell.
Re: Extraktion von Links aus der Skype-History
Verfasst: Sonntag 13. Mai 2012, 20:33
von BlackJack
@webspider: Das ist überhaupt nicht merkwürdig. Du erhälst *immer* Tupel. In der Datenbank steht an der Stelle ja auch ein Wert, nämlich ``NULL``. Der SQL-Wert ``NULL`` wird auf den Python-Wert `None` abgebildet und umgehkert.
Edit: Du könntest die SQL-Abfrage auch einfach um eine ``WHERE``-Klausel erweitern, die ``NULL``-Werte ausfiltert, dann brauchst Du auf der Python-Seite keinen extra Test dafür machen.
Edit2: Mit `lxml` statt `re` (ungetestet):
Code: Alles auswählen
#! /usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import print_function
import codecs
import sqlite3
from lxml import etree
#
# Replace with the path to main.db, the database containing your skype history.
#
HISTORY_FILENAME = (
'/Users/username/Library/Application Support/Skype/username/main.db'
)
OUTPUT_FILENAME = 'skype_links.txt'
SQL_STATEMENT = 'SELECT body_xml FROM Messages WHERE body_xml IS NOT NULL'
def main():
links = set()
connection = sqlite3.connect(HISTORY_FILE)
cursor = connection.cursor()
for message, in cursor.execute(SQL_STATEMENT):
links.update(etree.fromstring(message).xpath('//a/@href'))
connection.close()
with codecs.open(OUTPUT_FILE, 'w', 'utf-8') as output_file:
output_file.write('\n'.join(sorted(links)))
print('{0} links were written'.format(len(links)))
if __name__ == "__main__":
main()
Re: Extraktion von Links aus der Skype-History
Verfasst: Sonntag 13. Mai 2012, 21:07
von Dav1d
Wieso lxml, Python hat auch einen
etree in seiner Standard-Bibliothek.
Re: Extraktion von Links aus der Skype-History
Verfasst: Sonntag 13. Mai 2012, 21:15
von BlackJack
@Dav1d: Das kann aber nix. Zum Beispiel kein XPath.
Re: Extraktion von Links aus der Skype-History
Verfasst: Sonntag 13. Mai 2012, 21:28
von Dav1d
@BlackJack, braucht man auch nicht (zumindest hier), Beispiel aus der Doku:
Code: Alles auswählen
>>> from xml.etree.ElementTree import ElementTree
>>> tree = ElementTree()
>>> tree.parse("index.xhtml")
<Element 'html' at 0xb77e6fac>
>>> p = tree.find("body/p") # Finds first occurrence of tag p in body
>>> p
<Element 'p' at 0xb77ec26c>
>>> links = list(p.iter("a")) # Returns list of all links
>>> links
[<Element 'a' at 0xb77ec2ac>, <Element 'a' at 0xb77ec1cc>]
>>> for i in links: # Iterates through all found links
... i.attrib["target"] = "blank"
>>> tree.write("output.xhtml")
Re: Extraktion von Links aus der Skype-History
Verfasst: Montag 14. Mai 2012, 09:26
von lunar
@BlackJack: Du musst "lxml.html.fragments_fromstring()" verwenden, und jeden gefundenen Knoten einzeln durchsuchen, da Nachrichten in der Skype-Datenbank kein Root-Element haben, und mithin kein gültiges XML sind und mit ".fromstring()" nicht geparst werden können.
Statt "connection.close()" aufzurufen, kannst Du ein "with"-Block verwenden:
Mein Versuch:
Code: Alles auswählen
#! /usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import print_function, unicode_literals
import os
import sqlite3
from argparse import ArgumentParser
import lxml.html
DEFAULT_HISTORY = '~/Library/Application Support/Skype/{username}/main.db'
SQL_STATEMENT = 'SELECT body_xml FROM Messages WHERE body_xml IS NOT NULL'
def extract_links_from_message(message):
fragments = lxml.html.fragments_fromstring(message)
nodes = (f for f in fragments if not isinstance(f, basestring))
for node in nodes:
for link in node.xpath('//a/@href'):
yield link
def extract_links_from_history(filename):
with sqlite3.connect(filename) as connection:
cursor = connection.cursor()
for message, in cursor.execute(SQL_STATEMENT):
for link in extract_links_from_message(message):
yield link
def find_history_file(skype_username):
return os.path.expanduser(DEFAULT_HISTORY.format(username=skype_username))
def main(argv=None):
parser = ArgumentParser(description='Extract links from Skype history')
parser.add_argument('-s', '--sort', help='Sort links alphabetically',
action='store_true')
parser.add_argument('-u', '--unique', help='Print each link only once',
action='store_true')
parser.add_argument('username', help='The Skype username')
args = parser.parse_args(argv)
links = extract_links_from_history(find_history_file(args.username))
if args.unique:
links = set(links)
if args.sort:
links = sorted(links)
for link in links:
print(link)
if __name__ == "__main__":
main()