【问题标题】:Get element with a randomized class name获取具有随机类名的元素
【发布时间】:2018-05-31 20:23:34
【问题描述】:

看起来 Instagram 网页上的 <class id> 对应于 <img class> 每天都在变化。现在它是FFVAD,明天它会是别的东西。例如(我把它缩短了,链接很长):

<img class="FFVAD" alt="Tag your best friend" decoding="auto" style="" sizes="293px" src="https://scontent-lax3-2.cdninstagram.com/vp/0436c00a3ac9428b2b8c977b45abd022/5BAB3EBC/t51.2885-15/s640x640/sh0.08/e35/33110483_592294374461447_8669459880035221504_n.jpg">

也就是说,我需要修复脚本并对Class ID 进行硬编码,以便能够抓取网页。

var = driver.find_elements_by_class_name('FFVAD')

有人告诉我,我可以使用img.get_attribute('class') 找到class ID 并将其存储起来以备后用。但我仍然不明白这是如何实现的,因此 selenium 或 soup 可以从 html tag 中获取 Class ID 并稍后存储或解析它。

我现在得到的就是这个。这有点脏,而且不对,但想法就在那里。

import requests
import selenium.webdriver as webdriver

url = ('https://www.instagram.com/kitties')
driver = webdriver.Firefox()
driver.get(url)
last_height = driver.execute_script("return document.body.scrollHeight")

while True:
    imgs_dedupe = driver.find_elements_by_class_name('FFVAD')

    for img in imgs_dedupe:
        posts = img.get_attribute('class')
        print posts

    driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
    time.sleep(scroll_delay)
    new_height = driver.execute_script("return document.body.scrollHeight")

    if new_height == last_height:
        break
    last_height = new_height

当我运行它时,我得到了这个输出,因为页面上有 3 张图片,我得到了 3x Class ID

python tag_print.py 
FFVAD
FFVAD
FFVAD

【问题讨论】:

  • Instagram 需要注册才能访问任何内容,所以我无法给出具体示例。
  • 只有在检查元素时才能查看。无需注册或登录
  • 哦,所以首页也可以使用。变化很重要。
  • 是的,你可以去instagram.com/kitties查看所有内容,因为个人资料最长是公开的
  • 找到带有alt="Tag your best friend"的图像,获取它的类,然后用它来搜索具有相同类的其他元素。

标签: python selenium web-scraping


【解决方案1】:

您当前正在通过硬编码的类名搜索元素。

如果类名是随机的,则不能再对其进行硬编码。您必须:

  • 通过一些其他特征搜索元素(例如元素层次结构、一些其他属性等;XPath 可以做到这一点)

    In [10]: driver.find_elements_by_xpath('//article//img')
    Out[10]:
    [<selenium.webdriver.firefox.webelement.FirefoxWebElement (session="1ab4eeb4-10c4-4da4-996c-ee6744445dcc", element="55c48964-8cd0-4472-b35b-214a5a9bfbf7")>,
     <selenium.webdriver.firefox.webelement.FirefoxWebElement (session="1ab4eeb4-10c4-4da4-996c-ee6744445dcc", element="b7f7c8a4-e343-49ca-b416-49f72e67ae07")>,
     <selenium.webdriver.firefox.webelement.FirefoxWebElement (session="1ab4eeb4-10c4-4da4-996c-ee6744445dcc", element="728f6148-6a03-4c9a-9933-36859d65eb51")>]
    
    • 您还可以按元素的视觉特征进行搜索:大小、可见性、位置。但是,这不能仅由 XPath 完成,您必须获取所有 &lt;img&gt; 标签并使用 JS 手动检查每个标签。
      (请参阅下面的示例,因为它很长。)
  • 以某种方式从其他页面逻辑中学习这个类名(如果页面的逻辑本身可以找到并使用它,它必须存在于其他地方,并且该逻辑必须由其他东西找到,等等)

    在这种情况下,类名是 renderImage 函数中局部变量的一部分,因此只能通过 DOM 通过探索其 AST 来挽救它。该函数本身隐藏在webpack 机器内部的某个地方(它似乎将所有资源打包到几个具有单字母名称的全局对象中)。或者,您可以将所有包含的 JS 文件作为原始数据读取,并在其中查找 renderImage 的定义。因此,在这种情况下,尽管理论上仍然可行,但难度不成比例。


按视觉特征获取元素示例

在任何页面上,这将找到 3 张相同大小的图像,并排放置(这就是它们在https://www.instagram.com/kitties 的方式)。

由于HTMLElements 不能直接传递给 Python(至少,我找不到任何方法),我们需要传递一些唯一的 ID 来定位它们,比如唯一的 XPath。

(JS代码可能会更优雅,我对语言没有太多经验)

In [22]: script = """
  //https://stackoverflow.com/questions/2661818/javascript-get-xpath-of-a-node/43688599#43688599
  function getXPathForElement(element) {
      const idx = (sib, name) => sib 
          ? idx(sib.previousElementSibling, name||sib.localName) + (sib.localName == name)
          : 1;
      const segs = elm => !elm || elm.nodeType !== 1 
          ? ['']
          : elm.id && document.querySelector(`#${elm.id}`) === elm
              ? [`id("${elm.id}")`]
              : [...segs(elm.parentNode), `${elm.localName.toLowerCase()}[${idx(elm)}]`];
      return segs(element).join('/');
  }

  //https://plainjs.com/javascript/styles/get-the-position-of-an-element-relative-to-the-document-24/
  function offsetTop(el){
    return window.pageYOffset + el.getBoundingClientRect().top;
  }

  var expected_images=3;
  var found_groups=new Map();
  for (e of document.getElementsByTagName('img')) {
    let group_id = e.offsetWidth + "x" + e.offsetHeight;
    if (!(found_groups.has(group_id))) found_groups.set(group_id,[]);
    found_groups.get(group_id).push(e);
  }
  for ([k,v] of found_groups) {
    if (v.length != expected_images) {found_groups.delete(k);continue;}
    var offset_top = offsetTop(v[0]);
    for (e of v){
      let _c_oft = offsetTop(e);
      if (_c_oft !== offset_top){
        found_groups.delete(k);
        break;
      }
    }
  }
  if (found_groups.size != 1) {
    console.log(found_groups);
    throw 'Unexpected pattern of images after filtering';
  }

  var found_group = found_groups.values().next().value;


  result=[]
  for (e of found_group) {
    result.push(getXPathForElement(e));
  }
  return result;
"""

In [23]: d.execute_script(script)
Out[23]:
[u'id("react-root")/section[1]/main[1]/div[1]/article[1]/div[1]/div[1]/div[1]/div[1]/a[1]/div[1]/div[1]/img[1]',
 u'id("react-root")/section[1]/main[1]/div[1]/article[1]/div[1]/div[1]/div[1]/div[2]/a[1]/div[1]/div[1]/img[1]',
 u'id("react-root")/section[1]/main[1]/div[1]/article[1]/div[1]/div[1]/div[1]/div[3]/a[1]/div[1]/div[1]/img[1]']

In [27]: [d.find_element_by_xpath(xp) for xp in _]
Out[27]:
[<selenium.webdriver.firefox.webelement.FirefoxWebElement (session="1ab4eeb4-10c4-4da4-996c-ee6744445dcc", element="55c48964-8cd0-4472-b35b-214a5a9bfbf7")>,
 <selenium.webdriver.firefox.webelement.FirefoxWebElement (session="1ab4eeb4-10c4-4da4-996c-ee6744445dcc", element="b7f7c8a4-e343-49ca-b416-49f72e67ae07")>,
 <selenium.webdriver.firefox.webelement.FirefoxWebElement (session="1ab4eeb4-10c4-4da4-996c-ee6744445dcc", element="728f6148-6a03-4c9a-9933-36859d65eb51")>]

【讨论】:

  • 对于第二点,它看起来唯一出现的其他地方是&lt;style&gt;
  • @ivan_pozdeev 非常感谢您的详细解释!我正在拿它并保存到我的笔记中
  • @uzdisral 请注意,SO 上所有发布的内容都是 CC-BY-SA。
【解决方案2】:

所以我设法得到它(当然是在循环之外)

get_img_class = driver.find_elements_by_class_name('img')[1].get_attribute('class')

就像这样,我能够解析Class ID 并将其存储以备后用。非常感谢大家的帮助。所有的想法都很棒,并注明供以后使用。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-07-04
    • 2012-06-26
    • 2018-04-18
    • 1970-01-01
    • 2011-11-09
    • 2019-05-29
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多