【问题标题】:python - How would i scrape this website for specific data that's constantly changing/being updated?python - 我如何从这个网站上抓取不断变化/更新的特定数据?
【发布时间】:2018-06-26 23:37:15
【问题描述】:

该网站是: https://pokemongo.gamepress.gg/best-attackers-type

我的代码如下,暂时如下:

from bs4 import BeautifulSoup
import requests
import re

site = 'https://pokemongo.gamepress.gg/best-attackers-type'
page_data = requests.get(site, headers=headers)

soup = BeautifulSoup(page_data.text, 'html.parser')

check_gamepress = soup.body.findAll(text=re.compile("Strength"))
print(check_gamepress)

但是,我真的很想抓取某些数据,但我真的遇到了麻烦。 例如,我将如何抓取显示以下内容的部分以获得最佳错误类型:

"Good typing and lightning-fast attacks. Though cool-looking, Scizor is somewhat fragile."

这个信息显然可以像过去一样更新,当一个更好的口袋妖怪出现时。那么,我将如何在将来可能会更新的地方抓取这些数据,而无需在发生这种情况时进行代码更改。

提前感谢您的阅读!

【问题讨论】:

  • 太宽泛的问题。您可以尝试保存页面并检查差异。查看该列表是否以其他格式提供:rss、json、xml。
  • @LuisMuñoz 我只是想知道如何首先提取这些数据,正确的处理方法是什么?如果我每次都必须更新我的机器人我想这很好,但我肯定更想知道如何刮掉那部分,我会简单地用那句话的一部分改变“力量”,还是有办法找到它使用html标签?
  • 您可以尝试编写基于 Xpath 的解析器。这个 XPath 会得到一个包含 field__item class: '//div[@class="field__item]' 的所有 div 标签的列表。解析列表并保存为 cvs 或其他格式,然后使用它与未来版本进行比较。不是马上但如果你真的感兴趣,这是一个很好的项目:)。
  • @LuisMuñoz 感谢您的快速回复!感谢帮助。是的,我正在研究它并使用beautifulsoup 来搜索“field__item”,但是该页面上的几乎所有内容都使用该标签,因此它返回了很多信息,有没有办法更进一步以获得确切的数据我在找?我在 discord 上运行了一个机器人,基本上,每次有人键入 !data Water 时,我希望机器人能够提取所有关于最佳水类型的信息。目前,我的机器人只为该类型提供最好的口袋妖怪。
  • @sytech 这么认为!我想我可能只是做错了什么。所有“优势”等的模式不是也一样吗?我想我将不得不再次使用soup.body.find。谢谢!

标签: python web-scraping beautifulsoup request


【解决方案1】:

由于 HTML 的组织方式,这个特定的网站有点困难。包含信息的相关标签并没有太多的区别特征,所以我们必须要聪明一点。更复杂的是,包含整个页面信息的 div 是同级的。我们还必须用一些独创性来弥补这种对网页设计的讽刺。

我确实注意到整个页面(几乎完全)一致的模式。每个“类型”和基础部分分为 3 个 div:

  • 包含类型和口袋妖怪的 div,例如 Dark Type: Tyranitar
  • 包含“专业”和动作的 div。
  • 包含“评分”和评论的 div。

这里的基本思想是,我们可以通过一个大致如下的过程来开始组织这种标记混乱:

  1. 识别每个类型的标题 div
  2. 对于每个 div,通过访问其兄弟来获取其他两个 div
  3. 从每个 div 中解析信息

考虑到这一点,我提出了一个可行的解决方案。代码的主体由 5 个函数组成。一个用于查找每个部分,一个用于提取兄弟,三个函数用于解析每个 div。

import re
import json
import requests
from pprint import pprint
from bs4 import BeautifulSoup

def type_section(tag):
    """Find the tags that has the move type and pokemon name"""
    pattern = r"[A-z]{3,} Type: [A-z]{3,}"
    # if all these things are true, it should be the right tag
    return all((tag.name == 'div',
                len(tag.get('class', '')) == 1,
                'field__item' in tag.get('class', []),
                re.findall(pattern, tag.text),
                ))

def parse_type_pokemon(tag):
    """Parse out the move type and pokemon from the tag text"""
    s = tag.text.strip()
    poke_type, pokemon = s.split(' Type: ')
    return {'type': poke_type, 'pokemon': pokemon}

def parse_speciality(tag):
    """Parse the tag containing the speciality and moves"""
    table = tag.find('table')
    rows = table.find_all('tr')
    speciality_row, fast_row, charge_row = rows
    speciality_types = []

    for anchor in speciality_row.find_all('a'):
        # Each type 'badge' has a href with the type name at the end
        href = anchor.get('href')
        speciality_types.append(href.split('#')[-1])

    fast_move = fast_row.find('td').text
    charge_move = charge_row.find('td').text
    return {'speciality': speciality_types,
            'fast_move': fast_move,
            'charge_move': charge_move}

def parse_rating(tag):
    """Parse the tag containing categorical ratings and commentary"""
    table = tag.find('table')
    category_tags = table.find_all('th')
    strength_tag, meta_tag, future_tag = category_tags
    str_rating = strength_tag.parent.find('td').text.strip()
    meta_rating = meta_tag.parent.find('td').text.strip()
    future_rating = meta_tag.parent.find('td').text.strip()
    blurb_tags = table.find_all('td', {'colspan': '2'})

    if blurb_tags:
        # `if` to accomodate fire section bug
        str_blurb_tag, meta_blurb_tag, future_blurb_tag = blurb_tags
        str_blurb = str_blurb_tag.text.strip()
        meta_blurb = meta_blurb_tag.text.strip()
        future_blurb = future_blurb_tag.text.strip()
    else:
        str_blurb = None;meta_blurb=None;future_blurb=None

    return {'strength': {
                'rating': str_rating,
                'commentary': str_blurb},
            'meta': {
                'rating': meta_rating,
                'commentary': meta_blurb},
            'future': {
                'rating': future_rating,
                'commentary': future_blurb}
            }

def extract_divs(tag):
    """
    Get the divs containing the moves/ratings 
    determined based on sibling position from the type tag
    """
    _, speciality_div, _, rating_div, *_ = tag.next_siblings
    return speciality_div, rating_div

def main():
    """All together now"""
    url = 'https://pokemongo.gamepress.gg/best-attackers-type'
    response = requests.get(url)
    soup = BeautifulSoup(response.text, 'lxml')
    types = {}
    for type_tag in soup.find_all(type_section):
        type_info = {}
        type_info.update(parse_type_pokemon(type_tag))
        speciality_div, rating_div = extract_divs(type_tag)
        type_info.update(parse_speciality(speciality_div))
        type_info.update(parse_rating(rating_div))
        type_ = type_info.get('type')
        types[type_] = type_info
    pprint(types) # We did it
    with open('pokemon.json', 'w') as outfile:
        json.dump(types, outfile)

目前,整个事情只有一个小扳手。还记得我说过这种模式几乎完全一致吗?好吧,Fire 类型在这里是一个奇怪的球,因为它们包含了该类型的两个口袋妖怪,所以Fire 类型的结果不正确。我或一些勇敢的人可能会想出一个办法来解决这个问题。或者他们可能会在未来决定一只火系宝可梦。

可以在this gist 中找到此代码、生成的 json(美化)和使用的 HTML 响应存档。

【讨论】:

  • 非常感谢您的精彩解释!至于火灾类型,我的机器人最初在创建 Embed 时也遇到了问题,我做了这样的事情来解决这个问题:cut_gamepress_further = cut_gamepress.split(' & ') 然后 print(cut_gamepress_further[0]。可能必须改变所有我的代码现在与您的解释相匹配。再次感谢您!:) 解释得很好,完美无瑕!
  • 不用担心@sb2894。如果您对代码中的细节有任何疑问,请发表评论。干杯,欢迎来到 SO!
  • 很高兴终于来到这里!最近刚开始自学 Python 3,我对这个社区的真棒感到震惊!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-01-09
  • 2019-04-26
  • 1970-01-01
  • 2021-04-17
  • 2015-06-08
  • 2022-06-10
  • 1970-01-01
相关资源
最近更新 更多