【问题标题】:Scraping only new elements from website using requests and bs4使用请求和 bs4 仅从网站中抓取新元素
【发布时间】:2021-12-27 22:01:02
【问题描述】:

我想查看某个网站以从中收集数据,在第一次访问该网站时,我会收集所有数据以忽略它。如果添加了新行(例如此处的打印),我想执行某个操作。但是,每当出现新项目时,它似乎会打印网站上的每一行,即使我正在检查该行是否已经存在于字典中。不知道怎么解决,谁能看看?

import requests
import re
import copy
import time

from datetime import date
from bs4 import BeautifulSoup


class KillStatistics:
    def __init__(self):
        self.records = {}
        self.watched_names = ["Test"]
        self.iter = 0

    def parse_records(self):
        r = requests.get("http://149.56.28.71/?subtopic=killstatistics")
        soup = BeautifulSoup(r.content, "html.parser")
        table = soup.findChildren("table")

        for record in table:
            for data in record:
                if data.text == "Last Deaths":
                    pass
                else:
                    entry = data.text
                    entry = re.split("..?(?=[0-9][A-Z]).", data.text)
                    entry[0] = entry[0].split(", ")
                    entry[0][0] = entry[0][0].split(".")

                    entry_id, day, month, year, hour = (
                        entry[0][0][0],
                        entry[0][0][1],
                        entry[0][0][2],
                        entry[0][0][3],
                        entry[0][1],
                    )

                    message = entry[1]
                    nickname = (re.findall(".+?(?=at)", message)[0]).strip()
                    killed_by = (re.findall(r"(?<=\bby).*", message)[0]).strip()
                    if self.iter < 1:
                        """Its the first visit to the website, i want it to download all the data and store it in dictionary"""
                        self.records[
                            entry_id
                        ] = f"{nickname} was killed by {killed_by} at {day}-{month}-{year} {hour}"
                    elif (
                        self.iter > 1
                        and f"{nickname} was killed by {killed_by} at {day}-{month}-{year} {hour}"
                        not in self.records.values()
                    ):
                        """Here I want to look into the dictionary to check if the element exists in it, 
                        if not print it and add to the dictionary at [entry_id] so we can skip it in next iteration
                        Don't know why but whenever a new item appears on the website it seems to edit every item in the dictionary instead of just editing the one
                        that wasnt there"""
                        print(
                            f"{nickname} was killed by {killed_by} at {day}-{month}-{year} {hour}"
                        )
                        self.records[
                            entry_id
                        ] = f"{nickname} was killed by {killed_by} at {day}-{month}-{year} {hour}"
        print("---")
        self.iter += 1


ks = KillStatistics()
if __name__ == "__main__":
    while True:
        ks.parse_records()
        time.sleep(10)

entry_id 始终与其 500 行数据相同,并且它们的 id 为 1,2,3...500,最新的始终为 1。我知道我总是可以检查 1 以获得最新的,但有时例如10 个玩家可以同时死亡,所以我想检查他们是否有变化,并且只在新玩家上执行打印。

当前输出:

Velerion was killed by Rat and Cave Rat at 27-12-2021 16:53
Scrappy was killed by Cursed Queen at 27-12-2021 16:52
Velerion was killed by Rat at 27-12-2021 16:28
Velerion was killed by Rat at 27-12-2021 16:22
Velerion was killed by Rat at 27-12-2021 16:21
Velerion was killed by Rat at 27-12-2021 15:51
Shade was killed by Tentacle Slayer at 27-12-2021 15:46
Mr Yahoo was killed by Immortal Hunter at 27-12-2021 15:41
Scrappy was killed by Witch Hunter at 27-12-2021 15:39
Barbudo Arqueiro was killed by Seahorse at 27-12-2021 15:23
Emperor Martino was killed by Dark Slayer at 27-12-2021 15:14
Shade was killed by Tentacle Slayer at 27-12-2021 15:11
Head Hunter was killed by Demon Blood Slayer at 27-12-2021 15:09

预期输出:

Velerion was killed by Rat and Cave Rat at 27-12-2021 16:53

【问题讨论】:

  • entry_id 是您的密钥。你应该搜索那个,而不是那个长字符串。
  • 我现在尝试做类似的事情elif (self.iter &gt; 1and self.records[entry_id] != f"{nickname} was killed by {killed_by} at {day}-{month}-{year} {hour}" ):,但它的行为完全相同。 entry_id 始终与其 500 行数据相同,并且它们的 id 为 1,2,3...500,最新的始终为 1。我知道我总是可以检查 1 以获得最新的,但有时例如 10 个玩家可以同时死,所以我想检查它们是否有变化,并且只在新的上执行打印
  • 那么你不能使用entry_id作为你的字典键。您需要跟踪最新条目的时间戳,并在到达该时间戳时停止查找。我会在下面告诉你。

标签: python python-3.x beautifulsoup python-requests


【解决方案1】:

以下是我对您的处理方式所做的更改。我正在使用一个正则表达式来解析所有标题信息。这让我一次获得所有 7 个数字字段,加上匹配的总长度告诉我消息从哪里开始。

然后,我使用时间戳来确定哪些数据是新的。最新的条目总是第一个,所以我抓住了批次的第一个时间戳作为下一个的阈值。

然后,我将条目存储在列表而不是字典中。如果您真的不需要永远存储它们,而只是想打印它们,那么您根本不需要跟踪列表。

import requests
import re
import copy
import time

from datetime import date
from bs4 import BeautifulSoup

prefix = r"(\d+)\.(\d+)\.(\d+)\.(\d+)\, (\d+):(\d+):(\d+)"

class KillStatistics:
    def __init__(self):
        self.records = []
        self.latest = (0,0,0,0,0,0,0)
        self.watched_names = ["Test"]

    def parse_records(self):
        r = requests.get("http://149.56.28.71/?subtopic=killstatistics")
        soup = BeautifulSoup(r.content, "html.parser")
        table = soup.findChildren("table")
        latest = None

        for record in table:
            for data in record:
                if data.text == "Last Deaths":
                    continue
                entry = data.text
                mo = re.match(prefix, entry)
                entry_id, day, month, year, hour, mm, ss = mo.groups()
                stamp = tuple(int(i) for i in (year, month, day, hour, mm, ss))
                if latest is None:
                    latest = stamp

                if stamp > self.latest:
                    rest = entry[mo.span()[1]:]
                    i = rest.find(" at ")
                    j = rest.find(" by ")
                    nickname = rest[:i]
                    killed_by = rest[j+4:]
                    msg = f"{nickname} was killed by {killed_by} at {day}-{month}-{year} {hour}"
                    print( msg )
                    self.records.append( msg )
        print("---")
        self.latest = latest

ks = KillStatistics()
if __name__ == "__main__":
    while True:
        ks.parse_records()
        time.sleep(10)

【讨论】:

  • 感谢您的回答,但它仍然给我相同的结果,每当出现新记录时(因此它编辑第一行的内容),它会打印所有其他保持不变的行。 “ Velerion 在 27-12-2021 17:31 被 Rat 和 Cave Rat 杀死 Velerion 在 27-12-2021 17:12 Velerion 被 Rat 和 Cave Rat 在 27-12-2021 16:53 Scrappy 杀死在 27-12-2021 16:52 被诅咒女王杀死 Velerion 在 27-12-2021 16:28 被 Rat 杀死 只有第一个应该打印,因为当我第一次访问网站时其他人在那里
  • 该代码非常适合我。我得到了 500 条记录的转储,然后它开始打印“---”。现在,只是为了确保您没有将数据保存到磁盘。如果您杀死该应用程序并重新启动它,您将使用完整的设置重新开始。你希望这在运行中持续存在吗?
  • 不是真的,每次玩家死亡时管理员都会更新网站上的数据,所以我很想检查行的内容是否已经存在于字典或列表中,如果不存在(这意味着它一个全新的记录)它打印它。但是每当发生这种情况时,它都会打印表中不应该发生的每一行。我知道可能有错误或我没有考虑到的情况,但我找不到它
  • 你试过我的代码吗?它使用时间戳以更可靠的方式解决您的问题。将时间戳保存到文件很容易,这样它就可以在运行中存活下来。
  • 嗨,我当然试过了。这是一个非常好的解决方案,但据我所知,它触发了每条记录的打印,而不仅仅是在初始运行后的新记录。但是你的方法确实教会了我很多东西:)也许我的指示不是很清楚,所以我很抱歉,但英语不是我的母语
【解决方案2】:

我自己设法找到了解决方案。在这种特定情况下,我需要创建一个条目列表及其副本。然后在每次查找之后,我将新创建的列表与旧列表进行比较,并使用 set().difference() 返回新记录。

import requests
import re
import copy
import time

from datetime import date
from bs4 import BeautifulSoup


class KillStatistics:
    def __init__(self):
        self.records = []
        self.old_table = []
        self.watched_names = ["Test"]
        self.visited = False

    def parse_records(self):
        r = requests.get("http://149.56.28.71/?subtopic=killstatistics")
        soup = BeautifulSoup(r.content, "html.parser")
        table = soup.findChildren("table")
        self.records = []

        for record in table:
            for data in record:
                if data.text == "Last Deaths":
                    continue
                else:

                    entry = data.text
                    entry = re.split("..?(?=[0-9][A-Z]).", data.text)
                    entry[0] = entry[0].split(", ")
                    entry[0][0] = entry[0][0].split(".")

                    entry_id, day, month, year, hour = (
                        entry[0][0][0],
                        entry[0][0][1],
                        entry[0][0][2],
                        entry[0][0][3],
                        entry[0][1],
                    )
                    ## czas recordów jest w EST. Można konwertować na czas w Brazylii i w CET
                    message = entry[1]
                    nickname = (re.findall(".+?(?=at)", message)[0]).strip()
                    killed_by = (re.findall(r"(?<=\bby).*", message)[0]).strip()
                    record_to_add = (
                        f"{nickname},{killed_by},{day}-{month}-{year} {hour}"
                    )

                    self.records.append(record_to_add)

        if len(self.old_table) > 0:
            self.compare_records(self.old_table, self.records)
        else:
            print("Setting up initial data...")
        print("---")
        self.visited = True

    def compare_records(self, old_records, new_records):
        new_record = list(set(new_records).difference(old_records))
        if len(new_record) > 0:
            print("We got new record")
            for i in new_record:
                print(i)
                with open("/home/sammy/gp/new_records", "a") as f:
                    f.write(i + "\n")
        else:
            print("No new records")

class DiscordBot:
    def __init__(self):
        pass


if __name__ == "__main__":
    ks = KillStatistics()
    while True:
        ks.parse_records()
        ks.old_table = copy.deepcopy(ks.records)
        time.sleep(10)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-08-11
    • 2019-03-12
    • 2017-12-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多