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_BLOCK_THRESHOLD = 10;
// SyntaxError: Unexpected token 'const'
// See: https://github.com/ariya/phantomjs/issues/14521
var FRAME_RECURSION_BLOCK_THRESHOLD = 10;

function getAttributesFromAllTagsByName(tagName, attributeList) {
	var elements = 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(getAttributesFromAllTagsByName, "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(getAttributesFromAllTagsByName, "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([]);

	while (frames.length) {

		var f = frames.shift();

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

		if (f.length >= FRAME_RECURSION_BLOCK_THRESHOLD) {
			system.stderr.writeLine("E: The frame-recursion-block detection attacks!");
			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/bash

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.cache"
touch "$seen"

free_games | while read; 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/bash

set -e

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

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

cat <<! | grep -E -f - "$tmp" # | shuf -n 1 | wget -q -i - -O /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’