从键字典中,如果正则表达式为真,则在 python 中拆分为两个键和值

From key dictionary, split into both two keys and values in python if regex is true

当我发现下一个问题时,我正试图做一些网络抓取:

这些是我搜索的链接的嵌套字典输出:

d1 = {'Gaia Project': {'Jugadores': '1 a 4', 'Duración': '60 – 150 minutos', 'Edad': '12+', 'Dureza': '4.37', 'Precio': '59,46€', 'Género': 'Eurogame – Mayorías', 'Editorial': 'Maldito Games', 'Diseñador/a': 'Jens Drögemüller', 'Total': '8.5', 'Aspecto / Componentes': '8', 'Diversión': '8', 'Variabilidad': '9.5', 'Originalidad': '9', 'Mecánicas': '8.5', 'Nota de lectores10 Votos': '8.5'}}
d2 = {'Churchill': {'Jugadores': '1 a 3', 'Duración': '60 – 300 minutos', 'Edad': '14+', 'Dureza': '3.28', 'Precio': '71,96€', 'Género': 'Eurogame – Construcción de Rutas, Económico.', 'Editorial': 'GMT Games\xa0/\xa0Devir', 'Diseñador/a': 'Mark Herman', 'Total': '8.9', 'Aspecto / Componentes': '8.1', 'Interacción': '9.7', 'Variabilidad': '8', 'Originalidad': '8.7', 'Mecánicas': '9.2'}}

正如您在 d1 中看到的,最后一类提到:

'Nota de lectores10 Votos': '8.5'

我想拆分成两个键和值,所以字典应该是这样的(见最后):

{'Gaia Project': {'Jugadores': '1 a 4', 'Duración': '60 – 150 minutos', 'Edad': '12+', Dureza': '4.37', 'Precio': '59,46€', 'Género': 'Eurogame – Mayorías', 'Editorial': 'Maldito Games', 'Diseñador/a': 'Jens Drögemüller', 'Total': '8.5', 'Aspecto / Componentes': '8', 'Diversión': '8', 'Variabilidad': '9.5', 'Originalidad': '9', 'Mecánicas': '8.5', 'Nota de lectores': '8.5', 'N. Votes: 10 Votos'}}

这是我试过的:

pattern_votes= r' de lectores\d.*'
if key.startswith('Nota'): 
            lectores = category.split(pattern_votes)
            category.append(lectores[0],"N. Votes")
            value.append(lectores[1])

其中 category 将是 'N. Votes' 和 value '10 Votos'。

我也尝试了 if(filter(pattern_votes,d1)) 但显然没有任何反应。

这些分别是来自类别和值的列表:

category = ['Jugadores', 'Duración', 'Edad', 'Dureza', 'Precio', 'Género', 'Editorial', 'Diseñador/a', 'Total', 'Aspecto / Componentes', 'Diversión', 'Variabilidad', 'Originalidad', 'Mecánicas', 'Nota de lectores10 Votos']

value = ['1 a 4', '60 – 150 minutos', '12+', '4.37', '59,46€', 'Eurogame – Mayorías', 'Maldito Games', 'Jens Drögemüller', '8.5', '8', '8', '9.5', '9', '8.5', '8.5']

感谢您的帮助!

编辑 正如 Kuldeep 所建议的,这是我的代码:

最后这个字符串是我试过没用的


import requests
import re
from bs4 import BeautifulSoup
import os
from collections import defaultdict


link = "https://mishigeek.com/gaia-project-resena-en-solitario/"
link2 = "https://mishigeek.com/churchill-resena-en-espanol-es-un-wargame/"
#def get_ratings(review):   
# Capturo la cabecera de la petición HTTP

def get_info(link):
    headers = requests.utils.default_headers()


    headers.update(
        {
             'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.82 Safari/537.36',
         }
     )

    # Me conecto a la url con .get()
    sitemap_soup = requests.get(link, headers=headers)
    sitemap_soup.close()
    if (sitemap_soup.ok==True):
        
        soup = BeautifulSoup(sitemap_soup.text,features="html.parser")
        d= defaultdict(dict)
        key=[]   
        category=[]
        value=[]
        otros=[] # Other set of category and values that will have to split.
        pattern = r'-resena.*$'
        pattern_votes= r' de lectores\d.*'
        
        
        # Mediante los bucle for, se buscan todos los valores que coincida con el soup.select
        for each_part in soup.select('figure[class*="wp-block-table"]'):
            for each_part in soup.select('tr'):
                otros.append(each_part.get_text())
        split_items = (i.split(':') for i in otros[:8])
        category, value = zip(*split_items)
        category, value = map(list, (category, value))
        
        nombre = re.sub(pattern,'',os.path.basename(link[:-1])).replace('-', ' ').title()
        key.append(nombre)
        category.append("Total")
        
        for each_part in soup.select('div[class*="lets-review-block lets-review-block__final-score"]'):
                value.append(each_part.get_text())
                 
        for each_part in soup.select('div[class*="lets-review-block__crit__title lr-font-h"]'):
                category.append(each_part.get_text())
               
        for each_part in soup.select('div[class*="lets-review-block__crit__score"]'):
                value.append(each_part.get_text())
                
        for k in key:
           for c,v in zip(category,value):
               d[k][c]=v
        
        
            
        print(d)
        print(category)
        print(value)
        '''
        if key.startswith('Nota'): 
            lectores = category.split(pattern_votes)
            category.append(lectores[0],"N. Votos")
            value.append(lectores[1])
        '''

我们的步骤是什么:

  1. 遍历所有电影及其属性
  2. 查找具有特定名称的属性
  3. 提取票数
  4. 更新属性

让我们来实施它:

import re

pattern = r"Nota de lectores(\d+).*" # our pattern to match full key and extract number of votes

for movie, properties in movies.items(): # 1
    m = None
    for k, v in properties.items():
        if m := re.match(pattern, k): # 2, this syntax assumes python 3.8
            break
    if m is not None:
        # 4
        del properties[m.group(0)] # remove old key
        properties["Nota de lectores"] = v # store previous value
        properties["Votes"] = m.group(1) # 3

请注意,我们无法在循环期间更新属性,因为我们无法在迭代期间更改字典大小。

让我们从最小的问题开始:如何将 'Nota de lectores10 Votos' 拆分为 'Nota de lectores' 和 '10 Votos'。我的方法是使用 itertools 库:使用 takewhile 获取第一个数字之前的部分,使用 dropwhile 获取从第一个数字开始的部分。

import itertools
def split_before_number(text):
    """Split text into 2 parts: before the first digit and the rest."""
    def not_digit(c):
        """Return True if character c is not a digit."""
        return not c.isdigit()
    before = ''.join(itertools.takewhile(not_digit, text))
    after = ''.join(itertools.dropwhile(not_digit, text))
    return before, after

测试一下:

>>> split_before_number('Nota de lectores10 Votos')
('Nota de lectores', '10 Votos')

接下来,我想解决将一对key/value转换为1对或2对的问题:

# This pair 'Jugadores': '1 a 4'
# Becomes:  'Jugadores': '1 a 4'

# This pair: 'Nota de lectores10 Votos': '8.5'
# Becomes:   'Nota de lectores': '8.5'
# and        'N. Votes': '10 Votos'

对应的代码:

def split_key_and_value(key, value):
    if not key.startswith("Nota"):
        yield key, value
        return

    key1, value2 = split_before_number(key)
    yield key1, value
    yield "N. Votes", value2

测试一下:

>>> dict(split_key_and_value('Nota de lectores10 Votos', "8.5"))
{'Nota de lectores': '8.5', 'N. Votes': '10 Votos'}

>>> dict(split_key_and_value("Jugadores", "1 a 4"))
{'Jugadores': '1 a 4'}

有了这些函数,我们现在可以解决更大的问题:转换 d1 的值的键和值,我称之为 v1:

def transform(dict_object):
    """Split some specific keys and values and form a new dict."""
    new_dict_object = {}
    for original_key, original_value in dict_object.items():
        for key, value in split_key_and_value(original_key, original_value):
            new_dict_object[key] = value
    return new_dict_object

测试一下:

>>> d1 = {'Gaia Project': {'Jugadores': '1 a 4',
  'Duración': '60 – 150 minutos',
  'Edad': '12+',
  'Dureza': '4.37',
  'Precio': '59,46€',
  'Género': 'Eurogame – Mayorías',
  'Editorial': 'Maldito Games',
  'Diseñador/a': 'Jens Drögemüller',
  'Total': '8.5',
  'Aspecto / Componentes': '8',
  'Diversión': '8',
  'Variabilidad': '9.5',
  'Originalidad': '9',
  'Mecánicas': '8.5',
  'Nota de lectores10 Votos': '8.5'}}

>>> v1 = d1["Gaia Project"]

>>> transform(v1)
{'Jugadores': '1 a 4',
 'Duración': '60 – 150 minutos',
 'Edad': '12+',
 'Dureza': '4.37',
 'Precio': '59,46€',
 'Género': 'Eurogame – Mayorías',
 'Editorial': 'Maldito Games',
 'Diseñador/a': 'Jens Drögemüller',
 'Total': '8.5',
 'Aspecto / Componentes': '8',
 'Diversión': '8',
 'Variabilidad': '9.5',
 'Originalidad': '9',
 'Mecánicas': '8.5',
 'Nota de lectores': '8.5',
 'N. Votes': '10 Votos'}

现在我们可以转换 d1 值,我们可以在 d1 上应用该转换:

>>> d1 = {key: transform(value) for key, value in d1.items()}

>>> d1
{'Gaia Project': {'Jugadores': '1 a 4',
  'Duración': '60 – 150 minutos',
  'Edad': '12+',
  'Dureza': '4.37',
  'Precio': '59,46€',
  'Género': 'Eurogame – Mayorías',
  'Editorial': 'Maldito Games',
  'Diseñador/a': 'Jens Drögemüller',
  'Total': '8.5',
  'Aspecto / Componentes': '8',
  'Diversión': '8',
  'Variabilidad': '9.5',
  'Originalidad': '9',
  'Mecánicas': '8.5',
  'Nota de lectores': '8.5',
  'N. Votes': '10 Votos'}}