Подтвердить что ты не робот

Добавить значения ключей и отсортировать их по вступлению ключей в список словарей в Python

Я действительно новичок в Python, и я застрял в этой проблеме, которую мне нужно решить. У меня есть файл журнала из Apache Log, как показано ниже:

[01/Aug/1995:00:54:59 -0400] "GET /images/opf-logo.gif HTTP/1.0" 200 32511
[01/Aug/1995:00:55:04 -0400] "GET /images/ksclogosmall.gif HTTP/1.0" 200 3635
[01/Aug/1995:00:55:06 -0400] "GET /images/ksclogosmall.gif HTTP/1.0" 403 298
[01/Aug/1995:00:55:09 -0400] "GET /images/ksclogosmall.gif HTTP/1.0" 200 3635
[01/Aug/1995:00:55:18 -0400] "GET /images/opf-logo.gif HTTP/1.0" 200 32511
[01/Aug/1995:00:56:52 -0400] "GET /images/ksclogosmall.gif HTTP/1.0" 200 3635

Я должен вернуть 10 наиболее запрошенных объектов и их накопленные байты. Мне нужно включить только GET-запросы с успешными (HTTP 2xx) ответами.

Таким образом, приведенный выше журнал приведет к:

/images/ksclogosmall.gif 10905
/images/opf-logo.gif 65022

До сих пор у меня есть следующий код:

import re
from collections import Counter, defaultdict
from operator import itemgetter
import itertools
import sys

log_file = "web.log"
pattern = re.compile(
      r'\[(?P<date>[^\[\]:]+):(?P<time>\d+:\d+:\d+) (?P<timezone>[\-+]?\d\d\d\d)\] '
      + r'"(?P<method>\w+) (?P<path>[\S]+) (?P<protocol>[^"]+)" (?P<status>\d+) (?P<bytes_xfd>-|\d+)')

dict_list = []

with open(log_file, "r") as f:
    for line in f.readlines():
        if re.search("GET", line) and re.search(r'HTTP/[\d.]+"\s[2]\d{2}', line):
            try:
                log_line_data = pattern.match(line)
                path = log_line_data["path"]
                bytes_transferred = int(log_line_data["bytes_xfd"])
                dict_list.append({path: bytes_transferred})
            except:
                print("Unexpected Error: ", sys.exc_info()[0])
                raise
    f.close()

print(dict_list)

Этот код печатает следующий список словарей.

[{'/images/opf-logo.gif': 32511}, 
{'/images/ksclogosmall.gif': 3635}, 
{'/images/ksclogosmall.gif': 3635}, 
{'/images/opf-logo.gif': 32511}, 
{'/images/ksclogosmall.gif': 3635}]

Я не знаю, как это сделать, чтобы получить результат:

/images/ksclogosmall.gif 10905
/images/opf-logo.gif 65022

Этот результат в основном добавляет значения, соответствующие аналогичным ключам, отсортированным по количеству раз, когда конкретный ключ произошел в порядке desc.

Примечание. Я попытался использовать colllections.Counter без каких-либо изменений, здесь я бы хотел сортировать по количеству раз, когда произошел ключ.

Любая помощь будет оценена.

4b9b3361

Ответ 1

Вы можете использовать коллекции .Counter и update для добавления байтов, переданных для каждого объекта:

from collections import Counter
c = Counter()
for d in dict_list:
    c.update(d)
occurrences=Counter([list(x.keys())[0] for x in dict_list])
sorted(c.items(), key=lambda x: occurrences[x[0]], reverse=True)

Вывод:

[('/images/ksclogosmall.gif', 10905), ('/images/opf-logo.gif', 65022)]

Ответ 2

Во-первых, список словарей на самом деле не имеет смысла для этого типа данных. Поскольку у каждого словаря будет только одна пара ключ-значение, просто создайте список кортежей (или список namedtuples, если вы хотите больше читаемости).

tuple_list.append((path, bytes_transferred))

Теперь получение желаемого результата будет более простым. Я лично использовал defaultdict.

from collections import defaultdict

tracker = defaultdict(list)
for path, bytes_transferred in tuple_list:
    tracker[path].append(bytes_transferred)
# {'/images/ksclogosmall.gif': [3635, 3635, 3635], '/images/opf-logo.gif': [32511, 32511]}

print([(p, sum(b)) for p, b in sorted(tracker.items(), key=lambda i: -len(i[1]))])
# [('/images/ksclogosmall.gif', 10905), ('/images/opf-logo.gif', 65022)]

Ответ 3

Вы можете закодировать свой dict и сохранить значения в новом dict:

results = {}
for d in dict_list:
    for k, v in d.items():
        total = results.get(k, 0) # get previously stored value, 0 if none
        results[k] = total + v

Ответ 4

Это может быть не самое элегантное решение, однако оно будет работать:

freq = {}
with open('test.txt') as f:
    lines = f.read().splitlines()

    for line in lines:
        if 'GET' in line and 'HTTP' in line and '200' in line:
            path = line.split()[3]
            occur = int(line.split()[-1])
            freq[path] = freq.get(path, 0) + occur

frequency = {k: v for k, v in sorted(freq.items(), key=lambda x: x[1])}

Итак, для вашего предоставленного фрагмента журнала:

print(frequency)
>>> {'/images/ksclogosmall.gif': 10905, '/images/opf-logo.gif': 65022}

Ответ 5

другой вариант, две строки

....
path = log_line_data["path"]
if [x for x in range(len(dict_list)) if path in dict_list[x].keys()]:
    continue

Выход

[{'/images/opf-logo.gif': 32511}, {'/images/ksclogosmall.gif': 3635}]

Ответ 6

Если вы хотите свернуть

[{'/images/opf-logo.gif': 32511}, 
{'/images/ksclogosmall.gif': 3635}, 
{'/images/ksclogosmall.gif': 3635}, 
{'/images/opf-logo.gif': 32511}, 
{'/images/ksclogosmall.gif': 3635}]

В словарь и суммировать значения с одним и тем же ключом:

  • Создайте новый, пустой словарь
  • Проведите цикл через каждый словарь, проверьте, существует ли ключ в вашем новом словаре
  • Если ключ (путь к файлу) не существует, скопируйте его
  • Если он существует, добавьте значение

`` `

total = {}

for d in all:
    for k, v in d.items():
             if k in total:
                     total[k] += v
             else:
                     total[k] = v

print(total)
{'/images/opf-logo.gif': 65022, '/images/ksclogosmall.gif': 10905}