【问题标题】:How to copy specific text from the body of the email?如何从电子邮件正文中复制特定文本?
【发布时间】:2019-06-08 05:41:44
【问题描述】:
Option Explicit

Sub GetFromInbox()

Dim olApp As Outlook.Application
Dim olNs As Outlook.Namespace
Dim olFldr As Outlook.MAPIFolder
Dim olItms As Outlook.Items
Dim olMail As Variant
Dim i As Long

Set olApp = New Outlook.Application
Set olNs = olApp.GetNamespace("MAPI")
Set olFldr = olNs.GetDefaultFolder(olFolderInbox).Folders("impMail")
Set olItms = olFldr.Items

olItms.Sort "Subject"

For Each olMail In olItms
    If InStr(olMail.Subject, "SubjectoftheEmail") > 0 Then
        ThisWorkbook.Sheets("Fixings").Cells(2, 2).Value = olMail.Body

    End If
Next olMail

Set olFldr = Nothing
Set olNs = Nothing
Set olApp = Nothing
End Sub

此代码帮助我下载整个电子邮件正文,但我需要在单元格中使用特定的粗体文本。电子邮件正文始终如下。这些行总是以相同的顺序排列。所有线路始终存在。电子邮件中的所有姓名都可以提前知道。

此电子邮件仅供内部使用

@ABC4:请在系统中添加以下详细信息(2019 年 1 月 12 日):

12345_ABC_MakOpt --- 264532154.78
12345_ABC_GAPFee --- 145626547.80

谢谢

´ ---------------------------------- -------- '得到设置 '---------------------------------------------------- ----

    Dim wb As Workbook
    Dim rngEmailSubject As Range
    Dim rngInstrumentName As Range
    Dim rngDate As Range
    Dim rngAmount As Range
    Dim arrFixing() As typFixing
    Dim rngValue As Range

    Dim rowIdx As Integer
    Dim ix As Integer
    Dim fixingDate As Date

    With wb.Sheets("FixingFromEmail")

        Set rngInstrumentName = .Range("instrument.name")
        Set rngDate = .Range("Date")
        Set rngAmount = .Range("Amount")

        rowIdx = rngInstrumentName.Row
        ix = 0

        Do While True

            rowIdx = rowIdx + 1
             If Not IsEmpty(.Cells(rowIdx, rngInstrumentName.Column).Value) _
        Then

                ix = ix + 1

                ReDim Preserve arrFixing(1 To ix)
                arrFixing(ix).InstrumentName = .Cells(rowIdx, rngInstrumentName.Column).Value
                arrFixing(ix).Date = .Cells(rowIdx, rngDate.Column).Value
                arrFixing(ix).Amount = .Cells(rowIdx, rngAmount.Column).Value


            Else
                Exit Do
            End If

        Loop

    End With´

【问题讨论】:

    标签: regex excel vba outlook regex-group


    【解决方案1】:

    您的问题太模糊,无法给出具体答案。我所能提供的只是第一阶段的一些指导。

    你需要决定什么是固定的,什么是可变的。

    “@ABC4”是固定的吗? “@ABC4: please add the following detail in system (for”) 修复了吗?

    是否总是有两条数据线?是否有多个数据线作为示例?是这些行的格式:

    Xxxxxxx space hyphen hyphen hyphen space amount 
    

    我首先将文本正文分成几行。几乎可以肯定,这些行被回车换行打破了。测试:

    Dim Count As Long
    
    For Each olMail In olItms
    
      Debug.Print Replace(Replace(Mid$(olMailBody, 1, 200), vbCr, "{c}"), vbLf, "{l}" & vbLf)
      Count = Count + 1
      If Count >= 10 Then
        Exit For
      End If
    
    Next olMail
    

    输出将类似于以下十个(最多)副本:

    @ABC4: please add the following detail in system (for 12-Jan-2019):{c}{l}
    {c}{l}
    12345_ABC_MakOpt --- 264532154.78{c}{l}
    12345_ABC_GAPFee --- 145626547.80{c}{l}
    Are the characters between lines “{c}{l}” or “{l}” or something else?
    

    在下面的代码中,如果需要,替换vbCR & vbLf 然后运行它:

    Dim Count As Long
    Dim InxL As Long
    Dim Lines() As String
    
    For Each olMail In olItms
    
      Lines = Split(olMail.Body, vbCR & vbLf)
      For InxL = 0 to UBound(Lines)
        Debug.Print InxL + 1 & "  " & Lines(InxL)
      Next
      Count = Count + 1
      If Count >= 10 Then
        Exit For
      End If
    
    Next
    

    输出将类似于以下十个(最多)副本:

    0  
    1  @ABC4: please add the following detail in system (for 12-Jan-2019):
    2  
    3  12345_ABC_MakOpt --- 264532154.78
    4  12345_ABC_GAPFee --- 145626547.80
    5 
    

    现在您可以将文本主体视为线条。注意:第一行是数字 0。顶部从来没有空行吗?顶部总是有一个空行吗?它有变化吗?我将假设顶部总是有一个空行。如果假设不正确,则需要修改以下代码。

    如果第 1 行是“xxxxxxxxxx 日期):”您可以提取日期:

    Dim DateCrnt As Date
    Dim Pos As Long
    
    DateCrnt = CDate(Left$(Right$(Lines(1), 13), 11))
    

    Pos = InStr(1, Lines(1), "(for ")
    DateCrnt = CDate(Mid$(Lines(1), Pos + 5, 11))
    

    注意:这两种方法都取决于行尾,就像您在示例中显示的那样。如果有任何变化,您将需要处理该变化的代码。

    您现在可以使用如下代码拆分数据行:

    Dim NameCrnt As String
    Dim AmtCrnt As Double
    
    For InxL = 3 To UBound(Lines)
      If Lines(InxL) <> "" Then
        Pos = InStr(1, Lines(InxL), " --- ")
        If Pos = 0 Then
          Debug.Assert False   ' Line not formatted as expected
        Else
          NameCrnt = Mid$(Lines(InxL), 1, Pos - 1)
          AmtCrnt = Mid$(Lines(InxL), Pos + 5)
        End If
        Debug.Print "Date="& DateCrnt & "    " & "Name=" & NameCrnt & "   " & "Amount=" & AmtCrnt
      End If
    Next
    

    输出是:

    Date=12/01/2019    Name=12345_ABC_MakOpt   Amount=264532154.78
    Date=12/01/2019    Name=12345_ABC_GAPFee   Amount=145626547.8
    

    新部分显示如何将数据从电子邮件添加到工作表

    这是本节的第二个版本,因为 OP 改变了对所需格式的看法。

    下面的代码已经过测试,但我创建的虚假电子邮件看起来像你问题中的那个。所以可能需要进行一些调试。

    我创建了一个新工作簿和一个名为“Fixings”的新工作表,标题如下:

    处理我的虚假电子邮件后,工作表如下所示:

    行的顺序取决于找到电子邮件的顺序。您可能首先想要最新的。对工作表进行排序超出了此答案的范围。注意:是列标题告诉宏要记录哪些值。如果在电子邮件中添加了新行,请添加新的列标题,该值将被保存而不更改宏。

    除了一个例外,我不会解释我使用的 VBA 语句,因为很容易在网上搜索“VBA xxxxx”并找到语句 xxxxx 的规范。例外是使用两个集合来保存待处理的数据。其余的解释描述了我的方法背后的原因。

    要求会有所变化,但可能不会持续 6 个月或 12 个月。例如,经理需要不同的标题或不同顺序的列。您无法预测需要进行哪些更改,但您可以为更改做好准备。例如,在我的代码顶部我有:

    Const ColFixDate As Long = 1
    Const ColFixDataFirst As Long = 2
    Const RowFixHead As Long = 1
    Const RowFixDataFirst As Long = 2
    

    我本可以写Cells(Row, 1).Value = Date。这有两个缺点:(1)如果日期列被移动,您必须在代码中搜索访问它的语句,以及(2)您必须记住第 1 列或第 2 列或第 3 列中的内容,从而使您的代码更难读。我避免使用文字来表示行号或列号。输入 ColFixDataFirst 而不是 2 的额外努力很快就会得到回报。

    我注意到在添加到您的问题的代码中,您使用命名范围来实现相同的效果。 VBA 的一个问题是通常有几种方法可以达到相同的效果。我更喜欢常量,但我们每个人都必须选择自己喜欢的。

    我曾在一个处理过许多电子邮件和工作簿的部门工作过,这些电子邮件和工作簿是从外部收到的,其中包含有用的数据,我可以告诉你,它们的格式一直在变化。将有一个额外的空行或将删除现有的空行。将会有额外的数据,或者现有数据的顺序不同。作者做出他们认为会有所帮助的更改,但很少做任何有用的事情,例如询问接收者是否愿意更改,甚至警告他们更改。我见过的最糟糕的情况是两个数字列颠倒了,几个月都没有注意到。幸运的是,我没有参与其中,因为从我们的系统中删除错误数据然后导入正确的数据是一场噩梦。我检查我能想到的所有内容,并拒绝处理与我预期不完全一致的电子邮件。错误信息全部写入即时窗口,方便开发。您可能想要使用 MsgBox 或将它们写入文件。如果邮件处理成功,则不会被删除;它被移动到一个子文件夹,以便在再次需要时可以检索它。

    olMail 是 Outlook 常量。不要使用olMail 或任何其他保留字作为变量名。

    我使用了Session 而不是命名空间。它们应该是等效的,但我曾经遇到过无法诊断的命名空间问题,因此我不再使用它们。

    我不对电子邮件进行排序,因为您的代码没有利用对电子邮件进行排序的优势。也许您可以利用按 ReceivedTime 排序的优势,但我可以看到不容易避免的潜在问题。

    我以相反的顺序处理电子邮件,因为它们是按位置访问的。例如,如果将电子邮件 5 移动到另一个文件夹,则之前的电子邮件 6 现在是电子邮件 5,For 循环将跳过它。如果以相反的顺序处理电子邮件,您不会介意电子邮件 6 现在是电子邮件 5,因为您已经处理了该电子邮件。

    如果您未设置保存日期或金额的单元格的NumberFormat,它们将根据您所在国家/地区的 Microsoft 默认显示。我使用了我最喜欢的显示格式。换成你最喜欢的。

    在处理完整个电子邮件并提取所需数据之前,代码不会向工作表输出任何内容。这意味着必须存储来自早期数据行的数据,直到处理完所有行。我使用了两个CollectionsPendingNamesPendingAmts。这不是我将数据存储在我为自己编写的宏中的方式。我的问题是替代方法更复杂或需要更高级的 VBA。

    如果你有任何不明白的地方,请回来。

    Option Explicit
    Sub GetFromInbox()
    
      Const ColFixDate As Long = 1
      Const ColFixName As Long = 2
      Const ColFixAmt As Long = 3
      Const RowFixDataFirst As Long = 2
    
      Dim AmtCrnt As Double
      Dim ColFixCrnt As Long
      Dim DateCrnt As Date
      Dim ErrorOnEmail As Boolean
      Dim Found As Boolean
      Dim InxItem As Long
      Dim InxLine As Long
      Dim InxPend As Long
      Dim Lines() As String
      Dim NameCrnt As String
      Dim olApp As New Outlook.Application
      Dim olFldrIn As Outlook.Folder
      Dim olFldrOut As Outlook.Folder
      Dim olMailCrnt As Outlook.MailItem
      Dim PendingAmts As Collection
      Dim PendingNames As Collection
      Dim Pos As Long
      Dim RowFixCrnt As Long
      Dim StateEmail As Long
      Dim TempStg As String
      Dim WshtFix As Worksheet
    
      Set WshtFix = ThisWorkbook.Worksheets("Fixings")
      With WshtFix
        RowFixCrnt = .Cells(Rows.Count, ColFixDate).End(xlUp).Row + 1
      End With
    
      Set olApp = New Outlook.Application
      Set olFldrIn = olApp.Session.GetDefaultFolder(olFolderInbox).Folders("impMail")
      Set olFldrOut = olFldrIn.Folders("Processed")
    
      For InxItem = olFldrIn.Items.Count To 1 Step -1
    
        If olFldrIn.Items(InxItem).Class = Outlook.olMail Then
    
          Set olMailCrnt = olFldrIn.Items(InxItem)
    
          If InStr(olMailCrnt.Subject, "SubjectoftheEmail") > 0 Then
            Lines = Split(olMailCrnt.Body, vbCr & vbLf)
    
            'For InxLine = 0 To UBound(Lines)
            '  Debug.Print InxLine + 1 & "  " & Lines(InxLine)
            'Next
    
            StateEmail = 0    ' Before "please add ..." line
            ErrorOnEmail = False
            Set PendingAmts = Nothing
            Set PendingNames = Nothing
            Set PendingAmts = New Collection
            Set PendingNames = New Collection
    
            For InxLine = 0 To UBound(Lines)
              NameCrnt = ""     ' Line is not a data line
              Lines(InxLine) = Trim(Lines(InxLine))  ' Remove any leading or trailing spaces
    
              ' Extract data from line
              If Lines(InxLine) <> "" Then
                If StateEmail = 0 Then
                  If InStr(1, Lines(InxLine), "please add the ") = 0 Then
                    Debug.Print "Error with email received " & olMailCrnt.ReceivedTime & vbLf & _
                                "  The first non-blank line is" & vbLf & _
                                "    " & Lines(InxLine) & vbLf & _
                                "  but I was expecting something like:" & vbLf & _
                                "    @ABC4: please add the following detail in system (for 13-Jan-2019):"
                    ErrorOnEmail = True
                    Exit For
                  End If
                  TempStg = Left$(Right$(Lines(InxLine), 13), 11)
                  If Not IsDate(TempStg) Then
                    Debug.Print "Error with email received " & olMailCrnt.ReceivedTime & vbLf & _
                                "  The value I extracted from the ""please add the ...""" & _
                                " line is """ & vbLf & "  " & TempStg & _
                                """ which I do not recognise as a date"
                    ErrorOnEmail = True
                    Exit For
                  End If
                  DateCrnt = CDate(TempStg)
                  StateEmail = 1    ' After "please add ..." line
                ElseIf StateEmail = 1 Then
                  If Lines(InxLine) = "" Then
                    ' Ignore blank line
                  ElseIf Lines(InxLine) = "thanks" Then
                    ' No more data lines
                    Exit For
                  Else
                    Pos = InStr(1, Lines(InxLine), " --- ")
                    If Pos = 0 Then
                      Debug.Print "Error with email received " & olMailCrnt.ReceivedTime & vbLf & _
                                  "  Data line: " & Lines(InxLine) & vbLf & _
                                  "    does not contain ""---"" as required"
                      ErrorOnEmail = True
                      'Debug.Assert False
                      Exit For
                    End If
                    NameCrnt = Mid$(Lines(InxLine), 1, Pos - 1)
                    TempStg = Mid$(Lines(InxLine), Pos + 5)
                    If Not IsNumeric(TempStg) Then
                      Debug.Print "Error with email received " & olMailCrnt.ReceivedTime & vbLf & _
                                  "  Data line:" & Lines(InxLine) & vbLf & _
                                  "    value after ""---"" is not an amount"
                      ErrorOnEmail = True
                      'Debug.Assert False
                      Exit For
                    End If
                    AmtCrnt = CDbl(TempStg)
                  End If
                End If  ' StateEmail
              End If ' Lines(InxLine) <> ""
    
              If ErrorOnEmail Then
                ' Ignore any remaining lines
                Exit For
              End If
    
              If NameCrnt <> "" Then
                ' Line was a data line without errors. Save until know entire email is error free
                PendingNames.Add NameCrnt
                PendingAmts.Add AmtCrnt
              End If
    
            Next InxLine
    
            If Not ErrorOnEmail Then
              ' Output pending rows now know entire email is error-free
              With WshtFix
                For InxPend = 1 To PendingNames.Count
                  With .Cells(RowFixCrnt, ColFixDate)
                    .Value = DateCrnt
                    .NumberFormat = "d mmm yy"
                  End With
                  .Cells(RowFixCrnt, ColFixName).Value = PendingNames(InxPend)
                  With .Cells(RowFixCrnt, ColFixAmt)
                    .Value = PendingAmts(InxPend)
                    .NumberFormat = "#,##0.00"
                  End With
                  RowFixCrnt = RowFixCrnt + 1
                Next
              End With
              ' Move fully processed email to folder Processed
              olMailCrnt.Move olFldrOut
            End If
    
          End If  ' InStr(olMailCrnt.Subject, "SubjectoftheEmail") > 0
        End If  ' olFldrIn.Items(InxItem).Class = Outlook.olMail
    
      Next InxItem
    
      Set olFldrIn = Nothing
      Set olFldrOut = Nothing
      olApp.Quit
      Set olApp = Nothing
    
    End Sub
    

    【讨论】:

    • 感谢您的帮助,它真的帮助了我很多。但是我有一个小问题,输出它的打印量没有点,例如对我来说它的打印 26453215278 而不是 264532154.78?
    • 奇怪。我建议点击声明Debug.Print "Date="&amp; DateCrnt &amp; ...,然后点击F9。该语句应该变成棕色,表明它现在是一个断点。运行将在断点语句处停止的例程。将鼠标悬停在 AmtCrnt 的任何使用上。应该会出现一个包含AmtCrnt = 264532154.78 的小弹出窗口。如果显示正确,则说明您有打印问题。如果显示不正确,则说明存在分配问题。这会告诉我们去哪里找麻烦。
    • 如果您将AmtCrnt 声明为Long,您将得到AmtCrnt = 264532155,因为解释器会将264532154.78 舍入以得到一个整数。除此之外,我不知道如何抑制点。
    • 我注意到 Muhammad Ali 的回答建议使用 Regex,但我没有注意到您已将 regex 作为标签包含在内。您想要正则表达式解决方案吗?我不是正则表达式专家,巧合的是,我昨天问了一个正则表达式问题,并在我的问题中包含了我的测试工具。你可能会觉得它很有趣:stackoverflow.com/q/54181145/973283.
    • 感谢 Tony Dallimore 不幸的是,AmtCrnt = 264532154.78 在微小的弹出窗口中也出现如下 26453215478。
    【解决方案2】:

    如果你总是在第一行有一个日期,那么你可以用这样简单的东西来得到它: [0-9]{2}-[A-Za-z]{3}-[0-9]{4}

    在 regex101 上试试这个,看看 regex 的各个部分做了什么

    对于另一部分,我想最简单的方法是阅读整行

    【讨论】:

      猜你喜欢
      • 2014-01-05
      • 1970-01-01
      • 2023-03-20
      • 2017-01-16
      • 2013-05-23
      • 2011-05-15
      • 1970-01-01
      • 1970-01-01
      • 2022-05-16
      相关资源
      最近更新 更多