【问题标题】:How can I detect visited and unvisited links on a page?如何检测页面上已访问和未访问的链接?
【发布时间】:2011-11-09 14:18:19
【问题描述】:

我的目标是检测网页上未访问的链接,然后创建一个greasemonkey 脚本来点击这些链接。这里的未访问链接是指我没有打开的链接。由于我可以看到所有浏览器都提供了更改已访问和未访问链接颜色的功能,因此可以以任何方式检测这些链接。 在搜索时,我发现了这个链接:http://www.mozdev.org/pipermail/greasemonkey/2005-November/006821.html 但这里有人告诉我这不再可能了。请帮忙。

【问题讨论】:

    标签: javascript firefox hyperlink click greasemonkey


    【解决方案1】:

    正确,javascript 无法检测是否在 Firefox 或 Chrome 中访问了链接 - 这是唯一适用于此 Greasemonkey 上下文的 2 个浏览器。

    这是因为 Firefox 和 Chrome 非常重视安全和隐私。来自the CSS2 spec

    注意。样式表作者可能会滥用 :link 和 :visited 伪类来确定用户在未经用户同意的情况下访问了哪些站点。

    因此,UA 可能会将所有链接视为未访问的链接,或实施其他措施来保护用户的隐私,同时以不同的方式呈现已访问和未访问的链接。有关处理隐私的更多信息,请参阅 [P3P]。

    另请参阅,"Privacy and the :visited selector"
    您可以看到一个演示,显示安全浏览器不会让您在jsfiddle.net/n8F9U 嗅探访问过的链接。




    针对您的具体情况,由于您正在访问一个页面并使其保持打开状态,因此您可以帮助脚本跟踪访问过的链接。这不是万无一失的,但我相信它会满足您的要求。

    首先,通过执行以下操作查看正在运行的脚本

    1. 按原样安装脚本。
    2. 浏览到测试页面,jsbin.com/eledog
      每次重新加载或刷新时,测试页都会添加一个新链接。
    3. GM 脚本在其运行的页面上添加了 2 个按钮。左上角的“开始/停止”按钮和右下角的“清除”按钮。

      当您按下“开始”按钮时,它会执行以下操作:

      1. 页面上的所有现有链接都记录为“已访问”。
      2. 它启动一个计时器(默认设置:3 秒),当计时器关闭时,它会重新加载页面。
      3. 每次页面重新加载时,它都会打开所有新链接并启动新的重新加载计时器。
      4. 按“停止”按钮停止重新加载,访问的链接列表被保留。

      “清除”按钮,清除访问过的页面列表。
      警告:如果您在刷新循环处于活动状态时按“清除”,则下次重新加载页面时,所有链接将在新标签页中打开。


    接下来,在您的网站上使用脚本...

    仔细阅读脚本中的 cmets,您必须更改 @include@excludeselectorStr 值以匹配您正在使用的网站。

    为获得最佳效果,禁用任何“重新加载每个”插件或“自动更新”选项。


    重要提示:

    1. 脚本必须使用永久存储来跟踪链接。
      选项包括:cookie、sessionStoragelocalStorageglobalStorageGM_setValue()IndexedDB

      这些都有缺点,在这种情况下(单个站点,潜在的大量链接,多个会话),localStorage 是最好的选择(IndexedDB 可能是,但它仍然太不稳定 - 导致频繁FF 在我的机器上崩溃)。

      这意味着只能在每个站点的基础上跟踪链接,并且“安全”、“隐私”或“更清洁”实用程序可以阻止或删除已访问链接的列表。 (就像,清除浏览器的历史记录会重置访问链接的所有 CSS 样式。)

    2. 目前该脚本仅适用于 Firefox。即使安装了 Tampermonkey,它也不应该在 Chrome 上运行,而无需进行一些重新设计。



    脚本:

    /*******************************************************************************
    **  This script:
    **      1)  Keeps track of which links have been clicked.
    **      2)  Refreshes the page at regular intervals to check for new links.
    **      3)  If new links are found, opens those links in a new tab.
    **
    **  To Set Up:
    **      1)  Carefully choose and specify `selectorStr` based on the particulars
    **          of the target page(s).
    **          The selector string uses any valid jQuery syntax.
    **      2)  Set the @include, and/or, @exclude, and/or @match directives as
    **          appropriate for the target site.
    **      3)  Turn any "Auto update" features off.  Likewise, do not use any
    **          "Reload Every" addons.  This script will handle reloads/refreshes.
    **
    **  To Use:
    **      The script will place 2 buttons on the page: A "Start/Stop" button in
    **      the upper left and a "Clear" button in the lower left.
    **
    **      Press the "Start" button to start the script reloading the page and
    **      opening any new links.
    **      When the button is pressed, it is assumed that any existing links have
    **      been visited.
    **
    **      Press the "Stop" button to halt the reloading and link opening.
    **
    **      The "Clear" button erases the list of visited links -- which might
    **      otherwise be stored forever.
    **
    **  Methodology:
    **      Uses localStorage to track state-machine state, and to keep a
    **      persistent list of visited links.
    **
    **      Implemented with jQuery and some GM_ functions.
    **
    **      For now, this script is Firefox-only.  It probably will not work on
    **      Chrome, even with Tampermonkey.
    */
    // ==UserScript==
    // @name        _New link / visited link, tracker and opener
    // @include     http://jsbin.com/*
    // @exclude     /\/edit\b/
    // @require     http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js
    // @grant       GM_addStyle
    // ==/UserScript==
    /*- The @grant directive is needed to work around a design change
        introduced in GM 1.0.   It restores the sandbox.
    */
    
    //--- Key control/setup variables:
    var refreshDelay    = 3000;    //-- milliseconds.
    var selectorStr     = 'ul.topicList a.topicTitle';
    
    //--- Add the control buttons.
    $("body")  .append (  '<div id="GM_StartStopBtn" class="GM_ControlWrap">'
                        + '<button>Start checking for new links.</button></div>'
                )
               .append (  '<div id="GM_ClearVisitListBtn" class="GM_ControlWrap">'
                        + '<button>Clear the list of visited links.</button></div>'
                );
    $('div.GM_ControlWrap').hover (
        function () { $(this).stop (true, false).fadeTo ( 50, 1); },
        function () { $(this).stop (true, false).fadeTo (900, 0.8); }// Coordinate with CSS.
    );
    
    //--- Initialize the link-handler object, but wait until the load event.
    var stateMachine;
    window.addEventListener ("load", function () {
            stateMachine    = new GM_LinkTrack (    selectorStr,
                                                    '#GM_StartStopBtn button',
                                                    '#GM_ClearVisitListBtn button',
                                                    refreshDelay
                                                );
    
            /*--- Display the current number of visited links.
                We only update once per page load here.
            */
            var numLinks    = stateMachine.GetVisitedLinkCount ();
            $("body").append ('<p>The page opened with ' + numLinks + ' visited links.</p>');
        },
        false
    );
    
    
    /*--- The link and state tracker object.
        Public methods:
            OpenAllNewLinks ()
            StartStopBtnHandler ()
            ClearVisitedLinkList ()
            StartRefreshTimer ();
            StopRefreshTimer ();
            SetAllCurrentLinksToVisited ()
            GetVisitedLinkCount ()
    */
    function GM_LinkTrack (selectorStr, startBtnSel, clearBtnSel, refreshDelay)
    {
        var visitedLinkArry = [];
        var numVisitedLinks = 0;
        var refreshTimer    = null;
        var startTxt        = 'Start checking for new links.';
        var stopTxt         = 'Stop checking links and reloading.';
    
        //--- Get visited link-list from storage.
        for (var J = localStorage.length - 1;  J >= 0;  --J) {
            var itemName    = localStorage.key (J);
    
            if (/^Visited_\d+$/i.test (itemName) ) {
                visitedLinkArry.push (localStorage[itemName] );
                numVisitedLinks++;
            }
        }
    
        function LinkIsNew (href) {
            /*--- If the link is new, adds it to the list and returns true.
                Otherwise returns false.
            */
            if (visitedLinkArry.indexOf (href) == -1) {
                visitedLinkArry.push (href);
    
                var itemName    = 'Visited_' + numVisitedLinks;
                localStorage.setItem (itemName, href);
                numVisitedLinks++;
    
                return true;
            }
            return false;
        }
    
        //--- For each new link, open it in a separate tab.
        this.OpenAllNewLinks        = function ()
        {
            $(selectorStr).each ( function () {
    
                if (LinkIsNew (this.href) ) {
                    GM_openInTab (this.href);
                }
            } );
        };
    
        this.StartRefreshTimer      = function () {
            if (typeof refreshTimer != "number") {
                refreshTimer        = setTimeout ( function() {
                                            window.location.reload ();
                                        },
                                        refreshDelay
                                    );
            }
        };
    
        this.StopRefreshTimer       = function () {
            if (typeof refreshTimer == "number") {
                clearTimeout (refreshTimer);
                refreshTimer        = null;
            }
        };
    
        this.SetAllCurrentLinksToVisited = function () {
            $(selectorStr).each ( function () {
                LinkIsNew (this.href);
            } );
        };
    
        this.GetVisitedLinkCount = function () {
            return numVisitedLinks;
        };
    
        var context = this; //-- This seems clearer than using `.bind(this)`.
        this.StartStopBtnHandler    = function (zEvent) {
            if (inRefreshCycle) {
                //--- "Stop" pressed.  Stop searching for new links.
                $(startBtnSel).text (startTxt);
                context.StopRefreshTimer ();
                localStorage.setItem ('inRefreshCycle', '0'); //Set false.
            }
            else {
                //--- "Start" pressed.  Start searching for new links.
                $(startBtnSel).text (stopTxt);
                localStorage.setItem ('inRefreshCycle', '1'); //Set true.
    
                context.SetAllCurrentLinksToVisited ();
                context.StartRefreshTimer ();
            }
            inRefreshCycle  ^= true;    //-- Toggle value.
        };
    
        this.ClearVisitedLinkList   = function (zEvent) {
            numVisitedLinks = 0;
    
            for (var J = localStorage.length - 1;  J >= 0;  --J) {
                var itemName    = localStorage.key (J);
    
                if (/^Visited_\d+$/i.test (itemName) ) {
                    localStorage.removeItem (itemName);
                }
            }
        };
    
        //--- Activate the buttons.
        $(startBtnSel).click (this.StartStopBtnHandler);
        $(clearBtnSel).click (this.ClearVisitedLinkList);
    
        //--- Determine state.  Are we running the refresh cycle now?
        var inRefreshCycle  = parseInt (localStorage.inRefreshCycle, 10)  ||  0;
        if (inRefreshCycle) {
            $(startBtnSel).text (stopTxt); //-- Change the btn lable to "Stop".
            this.OpenAllNewLinks ();
            this.StartRefreshTimer ();
        }
    }
    
    //--- Style the control buttons.
    GM_addStyle ( "                                                             \
        .GM_ControlWrap {                                                       \
            opacity:            0.8;    /*Coordinate with hover func. */        \
            background:         pink;                                           \
            position:           fixed;                                          \
            padding:            0.6ex;                                          \
            z-index:            666666;                                         \
        }                                                                       \
        .GM_ControlWrap button {                                                \
            padding:            0.2ex 0.5ex;                                    \
            border-radius:      1em;                                            \
            box-shadow:         3px 3px 3px gray;                               \
            cursor:             pointer;                                        \
        }                                                                       \
        .GM_ControlWrap button:hover {                                          \
            color:              red;                                            \
        }                                                                       \
        #GM_StartStopBtn {                                                      \
            top:                0;                                              \
            left:               0;                                              \
        }                                                                       \
        #GM_ClearVisitListBtn {                                                 \
            bottom:             0;                                              \
            right:              0;                                              \
        }                                                                       \
    " );
    

    【讨论】:

    • 您忽略了 JavaScript 可以在各种安全上下文中运行的事实。 Greasemonkey 是一个 Firefox 插件,因此当然可以访问链接的访问状态。但是,我认为这不会暴露给用户脚本。
    • @Pumbaa80:附加组件确实可能可以检测到访问过的链接,但也不限于JS。无论如何,Greasemonkey 不会向 GM 脚本公开任何增强的样式或访问状态信息。
    • 感谢您在我的页面中回复良好,有一个自动更新选项我不知道它是如何工作的,但我已将其关闭(自动更新选项)并使用 firefox 插件不断刷新页面“重新加载每个“以检查新链接。如果这能回答您的问题,请告诉我。
    • 另外添加到我之前的评论中,我只想知道未访问的链接而不是访问的链接,修复后是否有可能?我还发现了一个网页,作者在安全修复后正在做类似的事情:webdesignfromscratch.com/html-css/…
    • @Chetan,第二篇文章只是造型技巧,对你没有帮助。但是,鉴于您的使用模式,有一种可能的解决方法。如果没有人能赶上我,我会在大约一天后解决问题并发布。
    【解决方案2】:

    您可以解析页面上的所有链接并获取它们的 CSS 颜色属性。如果链接的颜色与您在 CSS 中定义的未访问链接的颜色匹配,则此链接未访问。

    这种技术通常用于确定所有访问过的链接。 这是一种安全漏洞,可让您确定用户是否访问了特定网站。通常由低俗的营销人员使用。

    这类技巧通常归类为“浏览器历史操纵技巧”。

    更多信息与代码:http://www.stevenyork.com/tutorial/getting_browser_history_using_javascript

    【讨论】:

    • 没有。浏览器几年前就关闭了这个安全漏洞。亲自查看jsfiddle.net/n8F9U
    • 您可能希望/需要知道链接是否已被点击的许多正当理由。
    • jsfiddle 正确演示了 firefox 中的安全性,颜色对 javascript 是隐藏的。
    • 链接已损坏。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-09-26
    • 2018-07-14
    • 2012-05-18
    • 1970-01-01
    • 2011-10-28
    • 2013-07-20
    • 2023-04-02
    相关资源
    最近更新 更多