如何使用Python将单列文本文件解析为表格?

4

我是新来的StackOverflow用户,但我在这个网站上找到了很多答案。我也是一个编程新手,所以我想加入并最终成为这个社区的一部分——从一个问题开始,这个问题已经困扰了我几个小时。

我登录到一个网站,并抓取b标签内的大段文本,将其转换为适当的表格。生成的Output.txt的布局如下:

BIN                   STATUS                                                   
8FHA9D8H 82HG9F     RECEIVED SUCCESSFULLY AWAITING STOCKING PROCESS          


INVENTORY CODE:   FPBC   *SOUP CANS LENTILS                                 

BIN                   STATUS                                                   
HA8DHW2H HD0138     RECEIVED SUCCESSFULLY AWAITING STOCKING PROCESS          
8SHDNADU 00A123     #2956- INVALID STOCK COUPON CODE (MISSING).          
93827548 096DBR     RECEIVED SUCCESSFULLY AWAITING STOCKING PROCESS          

有许多页面都包含相同的区块,但我需要将它们组合成一个实际的表格,如下所示:
      BIN               INV CODE                          STATUS                                                   
HA8DHW2HHD0138     FPBC-*SOUP CANS LENTILS    RECEIVED SUCCESSFULLY AWAITING STOCKING PROCESS          
8SHDNADU00A123     FPBC-*SOUP CANS LENTILS    #2956- INVALID STOCK COUPON CODE (MISSING).          
93827548096DBR     FPBC-*SOUP CANS LENTILS    RECEIVED SUCCESSFULLY AWAITING STOCKING PROCESS          
8FHA9D8H82HG9F   SSXR-98-20LM NM CORN CREAM  RECEIVED SUCCESSFULLY AWAITING STOCKING PROCESS  

基本上,这个例子中所有的单独文本块都将成为这个表格的一部分,并且inv代码将重复显示其Bin值。我尝试过解析这些数据(使用了Pandas / bs / openpyxl / csv writer),但我不得不承认它们有点令人尴尬,因为我找不到任何关于这个特定问题的信息。还有没有好心人可以帮帮我? :)
(另外,我正在使用Python 2.7)
3个回答

0
一个简单的自定义解析器,像下面这样做应该就可以了。
from __future__ import print_function



def parse_body(s):
    line_sep = '\n'
    getting_bins = False
    inv_code = ''
    for l in s.split(line_sep):
        if l.startswith('INVENTORY CODE:') and not getting_bins:
            inv_data = l.split()
            inv_code = inv_data[2] + '-' + ' '.join(inv_data[3:])
        elif l.startswith('INVENTORY CODE:') and getting_bins:
            print("unexpected inventory code while reading bins:", l)
        elif l.startswith('BIN') and l.endswith('MESSAGE'):
            getting_bins = True
        elif getting_bins == True and l:
            bin_data = l.split()
            # need to add exception handling here to make sure:
            # 1) we have an inv_code
            # 2) bin_data is at least 3 items big (assuming two for
            #    bin_id and at least one for message)
            # 3) maybe some constraint checking to ensure that we have
            #    a valid instance of an inventory code and bin id
            bin_id = ''.join(bin_data[0:2])
            message = ' '.join(bin_data[2:])
            # we now have a bin, an inv_code, and a message to add to our table
            print(bin_id.ljust(20), inv_code.ljust(30), message, sep='\t')
        elif getting_bins == True and not l:
            # done getting bins for current inventory code
            getting_bins = False
            inv_code = ''

非常感谢您的回答。很抱歉这么晚才评论,工作日太长了。对于我所寻找的一切看起来都很完美,但由于某种原因控制台返回“None”。我已经使用类似的方法解析了Output.txt的一些部分,但我无法弄清楚我在您的代码中做错了什么。我是否遗漏了一些细节?(使用Python 2.x) - Patrick O
哦,好捕捉!我现在就测试一下,看看会发生什么。 - Patrick O
我的理解是,数据遵循一个重复的模式:<inv_code><blank_lines><bin_header><bins><blank_lines>。如果没有空行将bins与下一个inv_code分开,则会出现您描述的错误。如果“空白”行包含空格,则在for块中添加l.strip()作为第一行应该可以解决问题。基本上,像这样的自定义解析器需要识别数据中可靠的模式。如果OP数据示例不完整,则只有您可以查看足够的数据以识别必要的模式。 - Craig Burgler
最后一个仓库代码与下一个发票代码之间始终有两个空行,而在代码和下一个bin_header之间始终有一行空行。原帖中的模式基本上是整个文档所遵循的精确模式。我相信空行大约有半行的空格,但是当我在for下添加l.strip()时,它会将每个库存代码都返回为错误。 - Patrick O
让我们在聊天室里继续这个讨论 - Craig Burgler
显示剩余3条评论

0
一个相对复杂的问题,但这可能能为你提供一些启示:
import re, pandas as pd
from pandas import DataFrame

rx = re.compile(r'''
                (?:INVENTORY\  CODE:)\s*
                (?P<inv>.+\S)
                [\s\S]+?
                ^BIN.+[\n\r]
                (?P<bin_msg>(?:(?!^\ ).+[\n\r])+)
                ''', re.MULTILINE | re.VERBOSE)

string = your_string_here

# set up the dataframe
df = DataFrame(columns = ['BIN', 'INV', 'MESSAGE'])

for match in rx.finditer(string):
    inv = match.group('inv')
    bin_msg_raw = match.group('bin_msg').split("\n")

    rxbinmsg = re.compile(r'^(?P<bin>(?:(?!\ {2}).)+)\s+(?P<message>.+\S)\s*$', re.MULTILINE)
    for item in bin_msg_raw:
        for m in rxbinmsg.finditer(item):
            # append it to the dataframe
            df.loc[len(df.index)] = [m.group('bin'), inv, m.group('message')]

print(df)

说明

它查找 INVENTORY CODE 并设置组 (invbin_msg) 以便在 afterwork() 中进行进一步处理 (注意: 如果您只有一行 bin/msg,那么在此之后拆分组会更容易)。
然后,它拆分了 binmsg 部分,并将所有内容附加到 df 对象中。


不错!这肯定更加复杂,但我喜欢它生成字典列表的方式。我该如何通过DataFrame将这些列表转换为我发布的示例表格?让我困惑的部分是表格中有作为条目的列表。另外,我该如何让inv code像示例表格一样在每个bin中重复出现?谢谢 :) - Patrick O

-2

我有一个编写用于网站抓取的代码,可能会对你有所帮助。 基本上你需要做的是在网页上右键点击进入html,并尝试找到你要查找的表格标签,然后使用模块(我正在使用beautiful soup)提取信息。我正在创建一个json,因为我需要将其存储到mongodb中,你也可以创建表格。

#! /usr/bin/python

import sys
import requests
import re
from BeautifulSoup import BeautifulSoup
import pymongo

def req_and_parsing():
        url2 = 'http://businfo.dimts.in/businfo/Bus_info/EtaByRoute.aspx?ID='

        list1 = ['534UP','534DOWN']
        for Route  in list1:
                final_url = url2 + Route
                #r = requests.get(final_url)
                #parsing_file(r.text,Route)

        outdict = []
        outdict = [parsing_file( requests.get(url2+Route).text,Route)  for Route in list1 ]
        print outdict
        conn = f_connection()
        for i in range(len(outdict)):
                insert_records(conn,outdict[i])


def parsing_file(txt,Route):
        soup = BeautifulSoup(txt)
        table = soup.findAll("table",{"id" : "ctl00_ContentPlaceHolder1_GridView2"})
        #trtags = table[0].findAll('tr')

        tdlist = []
        trtddict = {}
        """
        for trtag in trtags:
                print 'print trtag- ' ,  trtag.text
                tdtags =  trtag.findAll('td')
                for tdtag in tdtags:
                        print tdtag.text
        """

        divtags = soup.findAll("span",{"id":"ctl00_ContentPlaceHolder1_ErrorLabel"})

        for divtag in divtags:
        for divtag in divtags:
                print "div tag - " , divtag.text
                if  divtag.text ==  "Currently no bus is running on this route" or "This is not a cluster (orange bus) route":
                        print "Page not displayed Errored with below meeeage for Route-", Route," , " , divtag.text
                        sys.exit()

        trtags = table[0].findAll('tr')
        for trtag in trtags:
                tdtags =  trtag.findAll('td')
                if len(tdtags) == 2:
                        trtddict[tdtags[0].text] = sub_colon(tdtags[1].text)
        return trtddict


def sub_colon(tag_str):
        return re.sub(';',',',tag_str)

def f_connection():
        try:
                conn=pymongo.MongoClient()
                print "Connected successfully!!!"
        except pymongo.errors.ConnectionFailure, e:
                print "Could not connect to MongoDB: %s" % e
        return conn

def insert_records(conn,stop_dict):
        db = conn.test
        print db.collection_names()
        mycoll = db.stopsETA
        mycoll.insert(stop_dict)

if __name__ == "__main__":
        req_and_parsing()

1
嘿!感谢您抽出时间来回答:)。我应该在我的帖子中提到,这整个文本体都在同一个标签(<b>)内。我必须在已经抓取它之后解析它,但我不太确定如何操作我的文本文件以获得最终的表格结果。 - Patrick O

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接