Backup von Forumbeiträgen?

-Loco- shared this question 5 months ago
Answered

Ich muss zugeben, dass ich dieses Forum in letzter Zeit selten aufgesucht habe und deshalb etwas überrascht über den Umzug nach Reddit war.


Nun stellen sich mir die Fragen:

  • Gibt es einen Thread in welchem ausgeführt wird wie mit diesem Forum im speziellen verfahren wird?
  • Kann ich irgendwo nachlesen wie es dazu kam (reine Neugier)?
  • Gibt es eine einfache Möglichkeit die eigenen Beiträge (im speziellen die Dateien) herunterzuladen ohne +X*10^Y Threads durch zu klicken oder werden diese nach der Migration irgendwo zugänglich sein?


Grüße

-Loco-

Comments (1)

photo
1

... scheinbar also "nach mir dir Sintflut".


Ich habe eine eigene Lösung gefunden und mir einen Webcrawler für das Forum gebastelt.

Es war/ist mir wichtig auf meine bisherigen Ergebnisse zugriff zu haben da ich gerne mal alte Lösungen aufgreife und wiederverwende (ich bin zu dumm um alles im Kopf zu behalten).


Da ich vermute, dass andere ebenfalls ein Backup anlegen möchten sind hier meine drei Skripte die ich nutze um mir alle Threads an denen ich beteiligt war und deren Dateien herunterzuladen.


Da ich mir die Arbeit in erster Linie nur für mich gemacht habe sind die Skripte nur für Linux+python3.


Bash Skript ggbSSID.sh um die Login SSID von Firefox zu erhalten um Benutzer Account Daten zu parsen (kann auch per Hand ausgelesen werden und in das Skript GGBcrawler01.py eingetragen werden ([Shift+F9] on Firefox und den Wert des SSID Cookies von GeoGebra abschreiben)).

    #!/bin/sh

    sqlfile=$(ls -t $HOME/.mozilla/firefox/*/cookies.sqlite | head -1)

    eval "cp $sqlfile /tmp/cookies.sqlite"

    sqlite3 -csv /tmp/cookies.sqlite "SELECT * FROM moz_cookies WHERE host GLOB '.geogebra.org' INTERSECT SELECT * FROM moz_cookies WHERE name GLOB 'SSID';" | cut -d ',' -f4


Python 3 Skript GGBcrawler01.py dieses liest alle Threads aus an welchen der Benutzer beteiligt war. Damit es korrekt funktioniert muss die Benutzer ID noch eingetragen werden. Diese findet sich in der Url wenn man im eigenen Forums Profil ist (https://help.geogebra.org/profile/<here is the USER ID>#/statistics). Dieses Skript speichert alle Thread Urls in einer CSV Datei in einem Unterordner BACKUP ab. Nur dieses Skript braucht die SSID.

    #!/usr/bin/env python3

    # coding: utf-8


    import os;

    import subprocess;

    import requests;

    import json;

    from bs4 import BeautifulSoup;

    import csv;

    import os;

    import re;

    from urllib.request import urlretrieve;

    from urllib.parse import urljoin;

    from urllib.parse import urlparse;

    cwd = os.getcwd();

    twd = os.path.join(cwd,'BACKUP');

    #user ssid from browser cookies after login (stay logged in)

    SSID = subprocess.run(['bash', 'ggbSSID.sh'], stdout=subprocess.PIPE).stdout.decode('utf-8').strip('\n');

    #user id from https://help.geogebra.org/p...

    USERID = XXXXX;

    SoupParser="lxml";


    def mkfolder(path):

    try:

    if not os.path.exists(path):

    os.mkdir(path);

    except OSError:

    print ("Creation of the directory %s failed" % path);

    def getfile(url,path,filename):

    try:

    urlretrieve(url, os.path.join(path,filename));

    except Error as err:

    print(err);


    headers = {

    'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0',

    'Accept': '*/*',

    'X-Requested-With': 'XMLHttpRequest',

    'Connection': 'keep-alive',

    'Cookie': 'SSID='+SSID,

    'Pragma': 'no-cache',

    'Cache-Control': 'no-cache',

    'TE': 'Trailers'

    };

    L = [];

    i = 1;

    ns = 0;

    np = set([1]);

    while i in np:

    print('requesting page: '+str(i));

    response = requests.get('https://help.geogebra.org/profile/'+str(USERID)+'?object_type=all&user_filter=commented&page='+str(i)+'&active_tab=topics',headers=headers)

    html = response.json()['response']['output'];

    for s in BeautifulSoup(html,features=SoupParser).body.find_all('h3',attrs={'class':'content-object-title'}):

    ns = ns + 1;

    e = {'set': i, 'number': ns, 'description': s.find_all('a')[0].text, 'url': s.find_all('a')[0]['href']};

    L.append(e);

    for s in BeautifulSoup(html,features=SoupParser).body.find_all('ul',attrs={'class':'paginator'})[0].find_all('a'):

    try:

    np.add(int(s.string.strip('\n')));

    except Exception:

    pass;

    i += 1;


    mkfolder(twd);

    with open(os.path.join(twd,'url_list.csv'), mode='w',encoding='utf-8') as file:

    writer = csv.DictWriter(file, delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL,fieldnames=L[0].keys());

    writer.writeheader()

    for i in L:

    writer.writerow(i);

    def trhify(elements):

    string = "<tr>";

    for s in elements:

    string += "<th>"+str(s)+"</th>";

    string += "</tr>\n";

    return string;

    def trify(s):

    string = "<tr>";

    string += "<td>"+str(s[0])+"</td>";

    string += "<td><a href=\""+str(s[2])+"\">"+str(s[1])+"</a></td>";

    string += "<td><a href=\""+str(s[0]).zfill(5)+"/index.html\">"+str(s[0]).zfill(5)+"</a></td>";

    string += "</tr>\n";

    return string;

    with open(os.path.join(twd,'url_table.html'), mode='w',encoding='utf-8') as file:

    file.write('<!DOCTYPE html><html><head><style>'

    'table{font-family: arial, sans-serif; border-collapse: collapse; width: 100\%;}'

    'td,th{border: 1px solid #dddddd; text-align: left; padding: 8px; } '

    'tr:nth-child(even){background-color: #dddddd;}'

    '</style></head><body>');

    file.write("<table style=\"width:100\%\">\n");

    file.write(trhify(['#','source','backup']));

    for i in L:

    file.write(trify(list(i.values())[1:]));

    file.write('</table>\n');

    file.write('</body></html>');


Python 3 Skript GGBcrawler02.py dieses liest die CSV Datei mit den Urls aus und speichert alle Threads in einem Unterordner in BACKUP. Dort werden auch alle GGb und Bilddateien der Threads gespeichert (es hat keine 100% Erfolgsquote da einige Threads oder Links korrumpiert sind). Für etwa 400 Threads habe ich 4h benötigt. Dieses Skript ist nicht optimiert da es nur Mittel zum Zweck ist (ein Skript) und ich die sonst schon langsammen Server nicht überfordern wollte. Das Skript arbeitet die CSV Datei Thread für Thread ab und entfernt alle erfolgreich gedownloadeten Threads aus der Liste. Alle Threads die scheitern werden an das Ende angehängt. Das Bedeutet, dass am Ende nur noch Fehlerhafte Threads in der Liste sind und sich diese unendlich wiederholen. Brecht das Skript also ab wenn nur noch Fehler in Terminal stehen (die Threads müsst Ihr entweder manuell herunterladen oder das Skript entsprechend anpassen). Es kann sein, dass der Server euch als Angreifer ausmacht (dann kommen fast nur 101 Fehler) wenn das passiert brecht das Skript ab und startet es nach einem Kaffee neu.

    #!/usr/bin/env python3

    # coding: utf-8


    import os;

    import subprocess;

    import requests;

    import json;

    from bs4 import BeautifulSoup;

    import csv;

    import os;

    import re;

    from urllib.request import urlretrieve;

    from urllib.parse import urljoin;

    from urllib.parse import urlparse;

    cwd = os.getcwd();

    twd = os.path.join(cwd,'BACKUP');

    SoupParser="lxml";


    def mkfolder(path):

    try:

    if not os.path.exists(path):

    os.mkdir(path);

    except OSError:

    print ("Creation of the directory %s failed" % path);

    def getfile(url,path,filename):

    try:

    urlretrieve(url, os.path.join(path,filename));

    except Exception as e:

    print('\033[93m'+'[ERROR]: could not download: '+ url +'\033[0m');

    raise;


    getfile('https://static.geogebra.org/static/css/help/styles.css',twd,'styles.css');

    getfile('https://help.geogebra.org/static/frontend_4_5.css?version=1',twd,'frontend_4_5.css');


    L = [];

    with open(os.path.join(twd,'url_list.csv'), mode='r') as csv_file:

    csv_reader = csv.DictReader(csv_file);

    line_count = -1;

    for row in csv_reader:

    line_count += 1;

    if line_count == 0:

    if 'set' in row.keys() and 'number' in row.keys() and 'description' in row.keys() and 'url' in row.keys():

    print('reading csv file...');

    else:

    sys.exit("could not read csv file...");

    L.append(row);

    print('['+str(len(L))+'] pages to parse...');


    def backup(l):

    headers = {

    'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0',

    'Accept': 'text/html',

    'Connection': 'keep-alive',

    'Cookie': 'Cookie: GeoGebraLangUI=en_GB; geogebra.applets=false',

    'Upgrade-Insecure-Requests': '1',

    'Pragma': 'no-cache',

    'Cache-Control': 'no-cache',

    'TE': 'Trailers'

    };

    imgcount = 0;

    filecount = 0;

    ctwd = os.path.join(twd,str(l['number']).zfill(5))

    mkfolder(ctwd);

    html = requests.get(l['url'],headers=headers).text;

    soup = BeautifulSoup(html,features=SoupParser);

    soup.find('div',attrs={'class' : 'rightarea'}).decompose();

    soup.find('div',attrs={'id' : 'ggbHeader'}).decompose();

    soup.find('div',attrs={'id' : 'top_navigation'}).decompose();

    soup.find('div',attrs={'class' : 'filter-area floatRight'}).decompose();

    soup.find('div',attrs={'id' : 'notifications'}).decompose();

    soup.find('div',attrs={'class' : 'breadcrumbs'}).decompose();

    soup.find('div',attrs={'class' : 'comments-title'}).decompose();

    soup.find('div',attrs={'class' : 'buttons-object-full'}).decompose();

    soup.find('link',attrs={'rel' : 'apple-touch-icon'}).decompose();

    soup.find('link',attrs={'rel' : 'manifest'}).decompose();

    soup.find('span',attrs={'class' : 'hidden show-print print-icon'}).decompose();

    if soup.find('div',attrs={'id' : 'ggbFooter'}):

    soup.find('div',attrs={'id' : 'ggbFooter'}).decompose();

    for tag in soup.find_all('div',attrs={'class' : 'leave-comment-block'}):

    tag.decompose();

    for tag in soup.find_all('div',attrs={'class' : 'comment-quick-reply'}):

    tag.decompose();

    for tag in soup.find_all('div',attrs={'class' : 'ico-man'}):

    tag.decompose();

    for tag in soup.find_all('div',attrs={'class' : 'object-full-statuses'}):

    tag.decompose();

    for tag in soup.find_all('a',attrs={'class' : 'rating-response'}):

    tag.decompose();

    for tag in soup.find_all('a',attrs={'class' : 'comment-reply'}):

    tag.decompose();

    for tag in soup.find_all('script'):

    tag.decompose();

    for tag in soup.find_all('link',attrs={'type' : 'text/css'}):

    tag.decompose();

    for tag in soup.find_all('link',attrs={'rel' : 'icon'}):

    tag.decompose();

    for tag in soup.find_all('link',attrs={'rel' : 'stylesheet'}):

    tag.decompose();

    soup = BeautifulSoup(re.sub(' +', ' ', re.sub('\\n+', '\n',re.sub('(<!--(.|\s|\n)*?-->\n)','',str(soup)))),features=SoupParser);

    link = soup.new_tag('link');

    link.attrs['href'] = "../styles.css";

    link.attrs['rel'] = "stylesheet";

    link.attrs['type'] = "text/css";

    soup.find("head").append(link)

    link.attrs['href'] = "../frontend_4_5.css";

    link.attrs['rel'] = "stylesheet";

    link.attrs['type'] = "text/css";

    link.attrs['media'] = "all";

    soup.find("head").append(link)

    mkfolder(os.path.join(ctwd,'images'));

    for i in soup.find_all('img'):

    imgcount += 1;

    try:

    parsedurl = urlparse(i['src']);

    except Exception as e:

    print('\033[93m'+'broken url: '+i['src']+'\n' + str(e) + '\033[0m');

    continue;

    if i['src'][0:5] == 'data:':

    continue;

    elif parsedurl.netloc == '':

    imgurl = urljoin('https://help.geogebra.org',i['src']);

    imgname = i['src'].rsplit('/', 1)[-1];

    elif parsedurl.netloc == 'latex.codecogs.com':

    imgurl = i['src'];

    if requests.get(i['src'], headers=headers).status_code == 404:

    continue;

    imgname = 'img_'+str(imgcount).zfill(5)+'_'+parsedurl.path.rsplit('/', 1)[-1].split('.',1)[0];

    else:

    if requests.get(i['src'], headers=headers).status_code == 404:

    continue;

    imgurl = i['src'];

    imgname = 'img_'+str(imgcount).zfill(5)+'_'+i['src'].rsplit('.', 1)[-1];

    try:

    getfile(imgurl,os.path.join(ctwd,'images'),imgname);

    i['src'] = 'images/'+imgname;

    except Exception as e:

    print('\033[93m'+'broken url: '+imgurl+'\n' + str(e) + '\033[0m');

    mkfolder(os.path.join(ctwd,'files'));

    for dl in soup.find_all('a',attrs={'target' : '_blank'}):

    filecount += 1;

    try:

    parsedurl = urlparse(dl['href']);

    except Exception as e:

    print('\033[93m'+'broken url: '+dl['href']+'\n' + str(e) + '\033[0m');

    continue;

    if parsedurl.netloc == 'ggbm.at':

    fileid = parsedurl.path.rsplit('/', 1)[-1];

    file = fileid+'.ggb';

    fileurl = "https://www.geogebra.org/ma...;

    elif parsedurl.netloc == 'help.geogebra.org' and parsedurl.path.split('/')[1] == 'attachments' and 'title' in dl.attrs:

    fileid = parsedurl.path.rsplit('/', 1)[-1];

    file = 'file_'+str(filecount).zfill(5)+'_'+dl['title'];

    fileurl = dl['href'];

    elif parsedurl.netloc == 'help.geogebra.org' and parsedurl.path.split('/')[1] == 'attachments' and not 'title' in dl.attrs:

    fileid = parsedurl.path.rsplit('/', 1)[-1];

    file = fileid+'.ggb';

    fileurl = dl['href'];

    else:

    print('Ignored: '+dl['href']);

    continue;

    try:

    print('Download: '+file);

    getfile(fileurl,os.path.join(ctwd,'files'),file);

    dl.string = 'files/'+file;

    dl.attrs['href'] = 'files/'+file;

    except Exception as e:

    print('\033[93m'+'broken url: '+fileurl+'\n' + str(e) + '\033[0m');

    with open(os.path.join(ctwd,'index.html'), "w", encoding='utf-8') as file:

    file.write(str(soup));


    ndone = 0;

    while L:

    l = L.pop(0);

    print('\033[92m'+'['+str(l['number']).zfill(5)+']'+'\033[0m'+' Page: '+l['description'] + ' - ' + str(ndone)+'/'+str(len(L)+1));

    try:

    backup(l);

    except Exception as e:

    L.append(l);

    with open(os.path.join(twd,'url_list.csv'), mode='w',encoding='utf-8') as file:

    writer = csv.DictWriter(file, delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL,fieldnames=L[0].keys());

    writer.writeheader()

    for i in L:

    writer.writerow(i);

    print('\033[93m'+'[ERROR]: page '+str(l['number']).zfill(5)+' job appended at end of cue (warning infinite loop!)\n'+

    ' keep sure to remove or repair faulty entries of the csv file! \n'+ str(e) +'\033[0m');

    raise

    ndone += 1;

    print('\033[92m'+' ####### all done ####### '+'\033[0m');


Als Bonus erzeugt das erste Skript im BACKUP Ordner eine Übersicht die die heruntergeladenen Threads verlinkt.

© 2021 International GeoGebra Institute