使用C#从Google Chrome获取当前标签页的URL

49

以前可以使用 FindWindowExSendMessage 结合使用,从 Google Chrome 中获取活动选项卡的 URL。最近的更新似乎破坏了这种方法,因为 Chrome 现在似乎是自己渲染所有内容。(您可以使用 Spy ++、AHK Window Spy 或 Window Detective 进行检查)

要在 Firefox 和 Opera 上获取当前 URL,可以使用 DDE 和 WWW_GetWindowInfo。但是在 Chrome 上似乎不再可能。

这个问题有一个答案,其中包含更多关于它是如何工作的信息,以下是代码片段(正如我所解释的,不再起作用 - hAddressBox0):

var hAddressBox = FindWindowEx(
    intPtr,
    IntPtr.Zero,
    "Chrome_OmniboxView",
    IntPtr.Zero);

var sb = new StringBuilder(256);
SendMessage(hAddressBox, 0x000D, (IntPtr)256, sb);
temp = sb.ToString();

所以我的问题是:是否有一种新的方法来获取当前聚焦的选项卡的URL?(仅标题是不够的)


请尝试查看这个SO问题 - Icemanind
9个回答

46

编辑: 看起来我在这里的答案中的代码不再适用于较新版本的Chrome(尽管仍然可以使用AutomationElement的想法),因此请查看其他答案以获取不同版本的解决方案。例如,这是适用于Chrome 54的一个例子:https://dev59.com/kmMk5IYBdhLWcg3w3xlY#40638519

以下代码似乎有效(感谢icemanind的评论),但它需要消耗大量资源。找到elmUrlBar大约需要350毫秒...有点慢。

更不用说我们还需要处理同时运行的多个chrome进程的问题。

// there are always multiple chrome processes, so we have to loop through all of them to find the
// process with a Window Handle and an automation element of name "Address and search bar"
Process[] procsChrome = Process.GetProcessesByName("chrome");
foreach (Process chrome in procsChrome) {
  // the chrome process must have a window
  if (chrome.MainWindowHandle == IntPtr.Zero) {
    continue;
  }

  // find the automation element
  AutomationElement elm = AutomationElement.FromHandle(chrome.MainWindowHandle);
  AutomationElement elmUrlBar = elm.FindFirst(TreeScope.Descendants,
    new PropertyCondition(AutomationElement.NameProperty, "Address and search bar"));

  // if it can be found, get the value from the URL bar
  if (elmUrlBar != null) {
    AutomationPattern[] patterns = elmUrlBar.GetSupportedPatterns();
    if (patterns.Length > 0) {
      ValuePattern val = (ValuePattern)elmUrlBar.GetCurrentPattern(patterns[0]);
      Console.WriteLine("Chrome URL found: " + val.Current.Value);
    }
  }
}

编辑:我对上面的缓慢方法不满意,所以我将其加速(现在为50毫秒),并添加了一些URL验证以确保我们获得正确的URL,而不是用户可能正在搜索网络上的内容或仍然忙于输入URL。以下是代码:

// there are always multiple chrome processes, so we have to loop through all of them to find the
// process with a Window Handle and an automation element of name "Address and search bar"
Process[] procsChrome = Process.GetProcessesByName("chrome");
foreach (Process chrome in procsChrome) {
  // the chrome process must have a window
  if (chrome.MainWindowHandle == IntPtr.Zero) {
    continue;
  }

  // find the automation element
  AutomationElement elm = AutomationElement.FromHandle(chrome.MainWindowHandle);

  // manually walk through the tree, searching using TreeScope.Descendants is too slow (even if it's more reliable)
  AutomationElement elmUrlBar = null;
  try {
    // walking path found using inspect.exe (Windows SDK) for Chrome 31.0.1650.63 m (currently the latest stable)
    var elm1 = elm.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.NameProperty, "Google Chrome"));
    if (elm1 == null) { continue; } // not the right chrome.exe
    // here, you can optionally check if Incognito is enabled:
    //bool bIncognito = TreeWalker.RawViewWalker.GetFirstChild(TreeWalker.RawViewWalker.GetFirstChild(elm1)) != null;
    var elm2 = TreeWalker.RawViewWalker.GetLastChild(elm1); // I don't know a Condition for this for finding :(
    var elm3 = elm2.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.NameProperty, ""));
    var elm4 = elm3.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.ToolBar));
    elmUrlBar = elm4.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Custom));
  } catch {
    // Chrome has probably changed something, and above walking needs to be modified. :(
    // put an assertion here or something to make sure you don't miss it
    continue;
  }

  // make sure it's valid
  if (elmUrlBar == null) {
    // it's not..
    continue;
  }

  // elmUrlBar is now the URL bar element. we have to make sure that it's out of keyboard focus if we want to get a valid URL
  if ((bool)elmUrlBar.GetCurrentPropertyValue(AutomationElement.HasKeyboardFocusProperty)) {
    continue;
  }

  // there might not be a valid pattern to use, so we have to make sure we have one
  AutomationPattern[] patterns = elmUrlBar.GetSupportedPatterns();
  if (patterns.Length == 1) {
    string ret = "";
    try {
      ret = ((ValuePattern)elmUrlBar.GetCurrentPattern(patterns[0])).Current.Value;
    } catch { }
    if (ret != "") {
      // must match a domain name (and possibly "https://" in front)
      if (Regex.IsMatch(ret, @"^(https:\/\/)?[a-zA-Z0-9\-\.]+(\.[a-zA-Z]{2,4}).*$")) {
        // prepend http:// to the url, because Chrome hides it if it's not SSL
        if (!ret.StartsWith("http")) {
          ret = "http://" + ret;
        }
        Console.WriteLine("Open Chrome URL found: '" + ret + "'");
      }
    }
    continue;
  }
}

2
添加 using System.Windows.Automation; - Codecat
1
这里有一个提示。在 Visual Studio 2010 中,您可以将光标放在未知标识符上,然后按下键盘上的 Ctrl+Dot。它会给您提供一系列可行的操作来解决问题。无论如何,您可能需要在项目引用中包含 UIAutomationTypes.dll - Codecat
@NelsonReis 我已经编辑了我的答案,在我的机器上适用于不同的语言。 - Codecat
7
请将以下两个引用添加到您的解决方案中:1.UIAutomationClient,2.UIAutomationTypes。这样做不会导致任何错误。 - Rama Subba Reddy M
3
你好,这对我以前非常有效,但最近由于Chrome升级到版本34.0.1847.116m,它已经停止工作了,因为Google改变了一些东西。请问有人能否建议任何修复方法和/或工具,以便找到句柄和属性等,以便进行修复? - QuietLeni
显示剩余6条评论

13

从Chrome 54开始,以下代码对我有效:

public static string GetActiveTabUrl()
{
  Process[] procsChrome = Process.GetProcessesByName("chrome");

  if (procsChrome.Length <= 0)
    return null;

  foreach (Process proc in procsChrome)
  {
    // the chrome process must have a window 
    if (proc.MainWindowHandle == IntPtr.Zero)
      continue;

    // to find the tabs we first need to locate something reliable - the 'New Tab' button 
    AutomationElement root = AutomationElement.FromHandle(proc.MainWindowHandle);
    var SearchBar = root.FindFirst(TreeScope.Descendants, new PropertyCondition(AutomationElement.NameProperty, "Address and search bar"));
    if (SearchBar != null)
      return (string)SearchBar.GetCurrentPropertyValue(ValuePatternIdentifiers.ValueProperty);
  }

  return null;
}

你试过在Chrome 55上用这个吗?似乎不起作用。随着获取这个的难度越来越大,似乎有意删除所有对URL的访问。 - Jon Limjap
有趣。我想知道这是否与我在Mac上运行Parallels有关。对我来说,searchBar在所有情况下都返回null。 - Jon Limjap
@JonLimjap: 我现在没有Mac,并且没有在该平台上进行过测试。可能是Chrome级别的差异(不太可能),或者是Parallel对Automation库的支持。您是否使用此方法成功运行其他版本的Chrome? - dotNET
在59.0.3071.86及其以前版本上验证通过 :) 但它几乎需要1013毫秒。 - Abdul Rauf
一旦我们拥有这个,Firefox和Chrome的CPU利用率就会飙升到80-90%。 - jan_kiran

10

对于Chrome V53及以上版本,我尝试了以上所有方法都没有成功。

以下是目前有效的方法:

Process[] procsChrome = Process.GetProcessesByName("chrome");
foreach (Process chrome in procsChrome)
{
    if (chrome.MainWindowHandle == IntPtr.Zero)
        continue;

    AutomationElement element = AutomationElement.FromHandle(chrome.MainWindowHandle);
    if (element == null)
        return null;
    Condition conditions = new AndCondition(
        new PropertyCondition(AutomationElement.ProcessIdProperty, chrome.Id),
        new PropertyCondition(AutomationElement.IsControlElementProperty, true),
        new PropertyCondition(AutomationElement.IsContentElementProperty, true),
        new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Edit));

    AutomationElement elementx = element.FindFirst(TreeScope.Descendants, conditions);
    return ((ValuePattern)elementx.GetCurrentPattern(ValuePattern.Pattern)).Current.Value as string;
}

在这里找到它:

https://social.msdn.microsoft.com/Forums/vstudio/en-US/93001bf5-440b-4a3a-ad6c-478a4f618e32/how-can-i-get-urls-of-open-pages-from-chrome-and-firefox?forum=csharpgeneral


7
我使用下面的代码得出了适用于Chrome 38.0.2125.10的结果(请将'try'块内的代码替换为此代码)。
var elm1 = elm.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.NameProperty, "Google Chrome"));
if (elm1 == null) { continue; }  // not the right chrome.exe
var elm2 = TreeWalker.RawViewWalker.GetLastChild(elm1);
var elm3 = elm2.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.HelpTextProperty, "TopContainerView"));
var elm4 = elm3.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.ToolBar));
var elm5 = elm4.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.HelpTextProperty, "LocationBarView"));
elmUrlBar = elm5.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Edit));

自Chrome 45(至少)以来,elm3 / elm4上发生了错误。仍在努力查明原因。 - Yves Schelpe

5

我采用了Angelo的解决方案,稍作修改......我对LINQ有着执念 :)

这就是所谓的主方法;它使用了几个扩展方法:

public IEnumerable<string> GetTabs()
{
  // there are always multiple chrome processes, so we have to loop through all of them to find the
  // process with a Window Handle and an automation element of name "Address and search bar"
  var processes = Process.GetProcessesByName("chrome");
  var automationElements = from chrome in processes
                           where chrome.MainWindowHandle != IntPtr.Zero
                           select AutomationElement.FromHandle(chrome.MainWindowHandle);

  return from element in automationElements
         select element.GetUrlBar()
         into elmUrlBar
         where elmUrlBar != null
         where !((bool) elmUrlBar.GetCurrentPropertyValue(AutomationElement.HasKeyboardFocusProperty))
         let patterns = elmUrlBar.GetSupportedPatterns()
         where patterns.Length == 1
         select elmUrlBar.TryGetValue(patterns)
         into ret
         where ret != ""
         where Regex.IsMatch(ret, @"^(https:\/\/)?[a-zA-Z0-9\-\.]+(\.[a-zA-Z]{2,4}).*$")
         select ret.StartsWith("http") ? ret : "http://" + ret;
}

请注意,注释有误导性,因为注释往往如此 - 它实际上并没有查看单个AutomationElement。我将其保留在那里,因为Angelo的代码中有它。
这是扩展类:
public static class AutomationElementExtensions
{
  public static AutomationElement GetUrlBar(this AutomationElement element)
  {
    try
    {
      return InternalGetUrlBar(element);
    }
    catch
    {
      // Chrome has probably changed something, and above walking needs to be modified. :(
      // put an assertion here or something to make sure you don't miss it
      return null;
    }
  }

  public static string TryGetValue(this AutomationElement urlBar, AutomationPattern[] patterns)
  {
    try
    {
      return ((ValuePattern) urlBar.GetCurrentPattern(patterns[0])).Current.Value;
    }
    catch
    {
      return "";
    }
  }

  //

  private static AutomationElement InternalGetUrlBar(AutomationElement element)
  {
    // walking path found using inspect.exe (Windows SDK) for Chrome 29.0.1547.76 m (currently the latest stable)
    var elm1 = element.FindFirst(TreeScope.Children,
      new PropertyCondition(AutomationElement.NameProperty, "Google Chrome"));
    var elm2 = TreeWalker.RawViewWalker.GetLastChild(elm1); // I don't know a Condition for this for finding :(
    var elm3 = elm2.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.NameProperty, ""));
    var elm4 = elm3.FindFirst(TreeScope.Children,
      new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.ToolBar));
    var result = elm4.FindFirst(TreeScope.Children,
      new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Custom));

    return result;
  }
}

5

我发现这篇文章,成功地使用以下方法在C#中从chrome中获取URL,谢谢大家!

不幸的是,最近的Chrome 69更新导致AutomationElement树遍历再次出现问题。

我发现了Microsoft的这篇文章:Navigate Among UI Automation Elements with TreeWalker

并使用它来制作一个简单的函数,该函数搜索我们要查找的"edit"控件类型的AutomationElement,而不是遍历始终在变化的树结构,然后从那个AutomationElement提取url值。

我编写了一个简单的类来封装所有这些内容:Google-Chrome-URL-Check-C-Sharp

自述文件中解释了如何使用它。

总体而言,它可能会更加可靠,希望你们中的一些人会发现它有用。


1
我刚在Github上为你的类添加了两个小修复。非常感谢你的努力工作!(它甚至可以在旧版Chrome 49上运行) - Fil

4
对于我来说,只有活动的Chrome窗口才具有MainWindowHandle。为了解决这个问题,我查找所有Chrome窗口,并使用这些句柄代替。例如:
    public delegate bool Win32Callback(IntPtr hwnd, IntPtr lParam);

    [DllImport("user32.dll")]
    protected static extern bool EnumWindows(Win32Callback enumProc, IntPtr lParam); 

    private static bool EnumWindow(IntPtr handle, IntPtr pointer)
    {
        List<IntPtr> pointers = GCHandle.FromIntPtr(pointer).Target as List<IntPtr>;
        pointers.Add(handle);
        return true;
    }

    private static List<IntPtr> GetAllWindows()
    {
        Win32Callback enumCallback = new Win32Callback(EnumWindow);
        List<IntPtr> pointers = new List<IntPtr>();
        GCHandle listHandle = GCHandle.Alloc(pointers);
        try
        {
            EnumWindows(enumCallback, GCHandle.ToIntPtr(listHandle));
        }
        finally
        {
            if (listHandle.IsAllocated) listHandle.Free();
        }
        return pointers;
    }

然后获取所有Chrome窗口:

    [DllImport("User32", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern int GetWindowText(IntPtr windowHandle, StringBuilder stringBuilder, int nMaxCount);

    [DllImport("user32.dll", EntryPoint = "GetWindowTextLength", SetLastError = true)]
    internal static extern int GetWindowTextLength(IntPtr hwnd);
    private static string GetTitle(IntPtr handle)
    {
        int length = GetWindowTextLength(handle);
        StringBuilder sb = new StringBuilder(length + 1);
        GetWindowText(handle, sb, sb.Capacity);
        return sb.ToString();
    }

最后:

GetAllWindows()
    .Select(GetTitle)
    .Where(x => x.Contains("Google Chrome"))
    .ToList()
    .ForEach(Console.WriteLine);

希望这篇文章可以帮助其他人节省时间,找到获取所有chrome窗口句柄的方法。


这实际上获取包含“Google Chrome”在标题中的任何窗口。(例如,在Internet Explorer中打开此网页。)另外,由于某种原因,返回的列表有一个额外的元素... - Derek Johnson
是的,但是一旦您运行其他答案中提到的算法,您就可以根据是否可以获取URL来确定哪些窗口是基于Chrome的。任何更全面的搜索都可以,对我来说问题是搜索过于独占(仅向我提供专注的Chrome窗口的进程)。 - yeerk

4

参考Angelo Geels的解决方案,这里提供适用于版本35的补丁程序 - "try"块内的代码必须替换为以下内容:

var elm1 = elm.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.NameProperty, "Google Chrome"));
if (elm1 == null) { continue; } // not the right chrome.exe
var elm2 = TreeWalker.RawViewWalker.GetLastChild(elm1); // I don't know a Condition for this for finding
var elm3 = elm2.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.NameProperty, ""));
var elm4 = TreeWalker.RawViewWalker.GetNextSibling(elm3); // I don't know a Condition for this for finding
var elm7 = elm4.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.ToolBar));
elmUrlBar = elm7.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Custom));  

我从这里获取了信息: http://techsupt.winbatch.com/webcgi/webbatch.exe?techsupt/nftechsupt.web+WinBatch/dotNet/System_CodeDom+Grab~URL~from~Chrome.txt

1
对于版本53.0.2785,使用以下代码使其正常工作:

var elm1 = elm.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.NameProperty, "Google Chrome"));
                if (elm1 == null) { continue; } // not the right chrome.exe
                var elm2 = elm1.FindAll(TreeScope.Children, new PropertyCondition(AutomationElement.NameProperty, ""))[1];
                var elm3 = elm2.FindAll(TreeScope.Children, new PropertyCondition(AutomationElement.NameProperty, ""))[1];
                var elm4 = elm3.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.NameProperty, "principal"));
                var elm5 = elm4.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.NameProperty, ""));
                elmUrlBar = elm5.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Edit));

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接