PhantomJS: Hyperlinks einer Website auflisten; mit Unterstützung für JavaScript und Frames

Friday, June 1, 2018

Dieses PhantomJS Skript listet alle Hyperlinks einer Website auf. Wobei JavaScript und Frames unterstützt werden (AJAX und Popups können noch ein Problem sein).

#!/usr/bin/phantomjs
// listlinks.js
// Lists all hyperlinks of an URL, supports JavaScript and Frames
// See also: http://phantomjs.org/api/
"use strict";

// Um Endlosrekursion in der Frametiefe zu erkennen und abzufangen
// FIXME: Make it a constant
// const FRAME_RECURSION_MAX_DEPTH = 10;
// SyntaxError: Unexpected token 'const'
// See: https://github.com/ariya/phantomjs/issues/14521
var FRAME_RECURSION_MAX_DEPTH = 10;

function getAttributesFromAllElementsByTagName(tagName, attributeList) {
	var elements = window.document.getElementsByTagName(tagName);

	var values = [];
	for (var i = 0; i < elements.length; i++) {
		values.push({});

		for (var j = 0; j < attributeList.length; j++) {
			values[i][attributeList[j]] = elements[i].getAttribute(attributeList[j]) || "";
		}

		values[i]["textContent"] = elements[i].textContent || "";
	}
	return values;
}

function listLinks () {
	var a = page.evaluate(getAttributesFromAllElementsByTagName, "a", ["href"]);

	for (var i = 0; i < a.length; i++) {
		// FIXME: Use the class URL to get an absolut URL
		// See: https://github.com/ariya/phantomjs/issues/14349
		var href = new URL(a[i]["href"], page.url).href || a[i]["href"];
		var textContent = a[i]["textContent"].replace(/\s+/g, " ");
		system.stdout.writeLine(href + "\t" + textContent);
	}
}

function getFrames (parrents) {
	var a = page.evaluate(getAttributesFromAllElementsByTagName, "iframe", []);

	var frames = [];
	for (var i = 0; i < a.length; i++) {
		frames.push(parrents.concat([i])); // frame position
	}
	return frames;
}

var system = require('system');

if (system.args.length != 2) {
	system.stderr.writeLine("Usage: " + system.args[0] + " <url>");
	phantom.exit(1);
}

var page = require("webpage").create();
page.settings.javascriptEnabled = true;
page.settings.resourceTimeout = 10*10000; // milliseconds
page.settings.userAgent = "";

page.open(system.args[1], function (status) {
	if (status !== "success") {
		system.stderr.writeLine("E: Fail to load URL: " + system.args[1]);
		phantom.exit(1);
	}

	listLinks();

	var frames = getFrames([]), f;

	while ((f = frames.shift()) !== undefined) {

		system.stderr.writeLine("I: Switching to frame: " + f.toString() + " frameName=" + page.frameName + " frameURL=" + page.frameUrl);

		if (f.length > FRAME_RECURSION_MAX_DEPTH) {
			system.stderr.writeLine("E: FRAME_RECURSION_MAX_DEPTH reached!");
			continue;
		}

		page.switchToMainFrame();
		for (var i = 0; i < f.length; i++) {
			page.switchToFrame(f[i]);
		}

		listLinks();

		frames = getFrames(f).concat(frames); // subframes

	}

	phantom.exit(0);
});

Download: attachs/listlinks.js

Benutzt wird es so:

phantomjs listlinks.js <URL>

Anwendungsfälle

Was kann man damit nun tun? Mir fallen folgende Konkrete Anwendungen ein:

  • Sich über Gratisaktionen von Steam Informieren lassen (Free Weekend/Free For a Limited Time)
  • Werbebanner (z.B. Google Ads/Ad Scence) von Websites anklicken

Sich über Gratisaktionen von Steam Informieren lassen (Free Weekend/Free For a Limited Time)

#!/bin/sh

set -e

free_games () {
	xvfb-run --auto-servernum phantomjs listlinks.js "https://store.steampowered.com/news/?headlines=1" | cut -f 2 | grep -i -w "free"
}

seen="${XDG_CACHE_HOME:-$HOME/.cache}/free-games-on-steam.seen"
touch "$seen"

free_games | while read REPLY; do
	if grep -q --line-regexp "$REPLY" "$seen"; then
		continue
	fi

	echo "$REPLY" | tee --append "$seen"
done

#cut-cache "$seen"

Das als Cronjob und man bekommt neue Gratisaktionen via Mail mitgeteilt.

Werbebanner (z.B. Google Ads/Ad Scence) von Websites anklicken

#!/bin/sh

set -e

temp="$(/bin/mktemp --tmpdir=/dev/shm/)"
trap "rm -f -- '$temp'" 0 1 2 3 15 # Lösche Datei beim Beenden, Abbruch ...

xvfb-run --auto-servernum phantomjs listlinks.js "$1" | cut -f 1 >"$temp"

cat <<! | grep -E -f - "$temp" # | shuf -n 1 | wget -i - -qO /dev/null
^https?://adclick\.g\.doubleclick\.net/
^https?://googleads\.g\.doubleclick\.net/
^https?://www\.googleadservices\.com/
!

Das funktioniert allerdings nicht immer/mit allen Websites (Ich tippe mal auf ein Timing-Problem o.ä.).

Anhang: Lösungen mit Selenium

Die Skripts nutzen z.Z. Chromium, aber Firefox sollte auch funktionieren (entsprechenden Code aktivieren).

PhantomJS funktioniert z.Z. nicht (könnte aber einzurichten sein):

Message: Error - Unable to load Atom ‘find_elements’ from file ‘:/ghostdriver/./third_party/webdriver-atoms/find_elements.js’