Script Python: Como cortar a saída para um tamanho de linha limitado?

1

Estou usando o script python para separar o domínio dos respectivos e-mails e, em seguida, agrupar os e-mails de acordo com o respectivo domínio. O script a seguir funciona para mim:

#!/usr/bin/env python3
from operator import itemgetter
from itertools import groupby
import os
import sys

dr = sys.argv[1]


for f in os.listdir(dr):
    write = []
    file = os.path.join(dr, f)
    lines = [[l.strip(), l.split("@")[-1].strip()] for l in open(file).readlines()]
    lines.sort(key=itemgetter(1))
    for item, occurrence in groupby(lines, itemgetter(1)):
        func = [s[0] for s in list(occurrence)]
        write.append(item+","+",".join(func))
    open(os.path.join(dr, "grouped_"+f), "wt").write("\n".join(write))

Eu usei: python3 script.py /path/to/input files
A entrada que eu dei foi uma lista de e-mails e saiu como:

domain1.com,[email protected],[email protected]
domain2.com,[email protected],[email protected],[email protected]

Mas qual é o problema que estou enfrentando é o limite do MongoDB. Como o MongoDB tem um limite de 16 MB de tamanho de documento e uma única linha no meu arquivo de saída é considerado como 1 documento pelo MongoDB e o tamanho da linha não deve ultrapassar 16 MB. Então, o que eu quero é que o resultado fique limitado a 21 e-mails por domínio e se o domínio tiver mais e-mails, ele deverá ser impresso em uma nova linha com os e-mails restantes (novamente se os e-mails excederem 21 e com o mesmo nome de domínio ). Eu cam armazenar dados duplicados no mongoDB.

Assim, o resultado final deve ser algo como o seguinte:

domain1.com,[email protected],[email protected],... [email protected]
domain1.com,[email protected],.....
domain2.com,[email protected],....

O ponto (.) no exemplo acima representa muitos textos, que eu cortei para facilitar a compreensão.
Espero que isso esclareça o meu problema e espere encontrar uma solução para isso.

    
por Jaffer Wilson 28.01.2017 / 10:06

2 respostas

1

Nova versão

O script que você postou realmente agrupa os e-mails por domínio, sem limite em número. Abaixo de uma versão que agrupará emails por domínio, mas divida a lista encontrada em partes arbitrárias. Cada pedaço será impresso em uma linha, começando com o domínio correspondente.

O script

#!/usr/bin/env python3
from operator import itemgetter
from itertools import groupby, islice
import os
import sys

dr = sys.argv[1]
size = 3

def chunk(it, size):
    it = iter(it); return iter(lambda: tuple(islice(it, size)), ())

for f in os.listdir(dr):
    # list the files
    with open(os.path.join(dr, "chunked_"+f), "wt") as report: 
        file = os.path.join(dr, f)
        # create a list of email addresses and domains, sort by domain
        lines = [[l.strip(), l.split("@")[-1].strip()] for l in open(file).readlines()]
        lines.sort(key=itemgetter(1))
        # group by domain, split into chunks
        for domain, occurrence in groupby(lines, itemgetter(1)):
            adr = list(chunk([s[0] for s in occurrence], size))
            # write lines to output file
            for a in adr:
                report.write(domain+","+",".join(a)+"\n")

Para usar

  • Copie o script em um arquivo vazio, salve-o como chunked_list.py
  • Na seção principal, defina o tamanho do bloco:

    size = 5
    
  • Execute o script com o diretório como argumento:

    python3 /path/to/chunked_list.py /path/to/files
    

    Em seguida, ele criará um arquivo editado de cada um dos arquivos, denominado chunked_filename , com os e-mails agrupados (em partes).

O que faz

O script recebe como entrada um diretório com arquivos como:

email1@domain1
email2@domain1
email3@domain2
email4@domain1
email5@domain1
email6@domain2
email7@domain1
email8@domain2
email9@domain1
email10@domain2
email11@domain1

De cada arquivo, ele cria uma cópia, como:

domain1,email1@domain1,email2@domain1,email4@domain1
domain1,email5@domain1,email7@domain1,email9@domain1
domain1,email11@domain1
domain2,email3@domain2,email6@domain2,email8@domain2
domain2,email10@domain2

(definir cunksize = 3)

    
por Jacob Vlijm 28.01.2017 / 11:15
1

Para suportar diretórios e arquivos grandes e arbitrários, você pode usar os.scandir() recebendo arquivos um por um e processando os arquivos linha por linha:

#!/usr/bin/env python3
import os

def emails_with_domain(dirpath):
    for entry in os.scandir(dirpath):
        if not entry.is_file():
            continue  # skip non-files
        with open(entry.path) as file:
            for line in file:
                email = line.strip()
                if email:  # skip blank lines
                    yield email.rpartition('@')[-1], email  # domain, email

Para agrupar os endereços de e-mail por domínio, não mais de 21 e-mails por linha, você pode usar collections.defaultdict() :

import sys
from collections import defaultdict

dirpath = sys.argv[1]
with open('grouped_emails.txt', 'w') as output_file:
    emails = defaultdict(list)  # domain -> emails
    for domain, email in emails_with_domain(dirpath):
        domain_emails = emails[domain]
        domain_emails.append(email)
        if len(domain_emails) == 21:
            print(domain, *domain_emails, sep=',', file=output_file)
            del domain_emails[:]  # clear

    for domain, domain_emails in emails.items():
        print(domain, *domain_emails, sep=',', file=output_file)

Nota:

  • todos os e-mails são salvos no mesmo arquivo
  • linhas com o mesmo domínio não são necessariamente adjacentes

Veja Qual é a maneira mais "pítnica" de iterar uma lista em blocos?

    
por jfs 30.01.2017 / 04:22