我试图通过使用Python 3.7和BeautifulSoup来拉动公司名称(如果该公司已获批准或其他),以及他们是否从此CT许可证列表中向住宅和/或企业进行营销,来简化我的数据收集:

http://www.dpuc.state.ct.us/electric.nsf/$FormByElectricApplicantsView?OpenForm&Start=1&Count=1000&ExpandView

如果我能获得有关如何完成代码的帮助,以便至少在Python中创建一个附加公司名称的列表,那将是一个很大的帮助。

我正在学习,并且能够连接到该站点并提取源代码。

到目前为止,我知道代码的这一部分可以正常工作,但不确定从何处去:

import requests
from bs4 import BeautifulSoup
result = requests.get("http://www.dpuc.state.ct.us/electric.nsf/$FormByElectricApplicantsView?OpenForm&Start=1&Count=1000&ExpandView")
src = result.content
soup = BeautifulSoup(src,'lxml')


我可以在源代码中看到公司名称,但不确定将其提取的最佳方法以及将其提取到列表中的最佳方法:

<a href="/electric.nsf/c39dc573ab1299538525743b004d4df6/719f088ca20a6a1f85257dfd00480e13?OpenDocument">3Degrees Group, Inc.</a>


我很希望能够将所有这些内容导入到csv中。在某个时候提交文件,其中包含公司,许可证的状态以及他们的市场对象,但是如果有人可以帮助我完成代码以将公司放在Python列表中,那将不胜感激,并允许我通过示例进行学习。

最佳答案

使用bs4 4.7.1+,您可以使用:contains:has来仅过滤与Supplier有关的部分。您可以进一步将df子集化为感兴趣的列。



tl; dr;

发出请求并将响应读入汤对象:

r = requests.get('http://www.dpuc.state.ct.us/electric.nsf/$FormByElectricApplicantsView?OpenForm&Start=1&Count=1000&ExpandView')
soup = bs(r.content, 'lxml')


让我们用一些图像来说明下一步。

我们可能会感到困惑的是,有很多嵌套表,但是我们可以从考虑感兴趣的“顶层”表开始:

python - 从&lt;a&gt;标记内的网页中提取公司名称-LMLPHP

我们可以从中获取信息(将鼠标悬停在实际html中该图像的不同trs上,然后观察页面上突出显示的内容。可以在浏览器搜索框中输入p:nth-child(4) > table来隔离此表):

python - 从&lt;a&gt;标记内的网页中提取公司名称-LMLPHP

的确,实际的可见内容是嵌套的,但我们知道所有感兴趣的内容都在trs之内,在给定级别上是一系列同级的trs

这一点

soup.select('tr:has(td:nth-of-type(1) font:contains(Supplier)) ~ tr:not(:has(td:nth-of-type(1) font:contains(Aggregator)), :has(td:nth-of-type(1) font:contains(Aggregator)) ~ tr)')


收集具有包含trstr的子font节点的“顶级” Supplier的同级tr:has(td:nth-of-type(1) font:contains(Supplier)),然后删除包含tr的“顶级” Aggregator及其同级。由于html中有很长的trs列表,因此您只想过滤掉感兴趣的部分之后的内容。

看一下第一部分(我使用select_one而不是select来演示匹配的第一个节点,而不是当我们添加general sibling combinator时匹配的多个同级兄弟-稍后会详细介绍) :

soup.select_one('tr:has(td:nth-of-type(1) font:contains(Supplier))')


运行上面的给出:

python - 从&lt;a&gt;标记内的网页中提取公司名称-LMLPHP

将此与页面进行比较:

python - 从&lt;a&gt;标记内的网页中提取公司名称-LMLPHP

您将看到我们如何找到一种从顶层(尽管嵌套)包含trs标签开始选择顶层Supplier的方法。但是,我们知道顶层是平坦的,因此我们需要删除(包括)Aggregator标签中的所有内容。因此,我们将:not伪类添加到该通用同级组合器中。简而言之,我们说获得除兄弟姐妹之外的所有兄弟姐妹的所有trs

python - 从&lt;a&gt;标记内的网页中提取公司名称-LMLPHP

现在,我们有了感兴趣的行的子集(绿色矩形)。我们对这些元素进行:not,并且每次找到与for loop匹配的节点时,都会将'td:nth-of-type(2) font'设置为找到的值,例如status ...

python - 从&lt;a&gt;标记内的网页中提取公司名称-LMLPHP

这行:

if node is not None:


正在检查当前Approved, Pending是否包含“状态”子节点,例如tr(我将其命名为Approved/Pending作为输出),并相应地为此类别设置以后的行标识符。

如果status没有此子节点,则我们知道它是容纳tr的其他trs之一,带有附加的信息列,例如:

python - 从&lt;a&gt;标记内的网页中提取公司名称-LMLPHP

由于嵌套的级别,有一些空的tds包括我们不想要的。我们稍后使用大熊猫将其删除:

df.drop([1,2,10], axis=1, inplace=True)


由于我们希望tds标签和所有status都在一个列表tds中,因此我使用row扩展了第一个列表以包括第二个列表。我相信这比extend快,但希望您对此感到满意。

因此,我们可以从例如:

['Approved']




['', '', 'Yes', 'Yes', '3Degrees Group, Inc.', 'http://www.3degreesinc.com', '235 Montgomery Street, Suite 320 San Francisco, CA 94104', '(866) 476-9378', '11-11-07 12/14/2011', '']




['Approved', '', '', 'Yes', 'Yes', '3Degrees Group, Inc.', 'http://www.3degreesinc.com', '235 Montgomery Street, Suite 320 San Francisco, CA 94104', '(866) 476-9378', '11-11-07 12/14/2011', '']


这些行将添加到名为insert的列表中。因此,您有一个列表列表(每行)。

您可以将此列表传递给pandas.DataFrame以使用to_csv方法生成准备好进行csv导出的数据框。您使用final参数指定标题。

生成行时:

tds = [td.text for td in tr.select('td')]


我们偶尔会发现其他空格和columns(换行符),例如

['', '', '', '', 'WFM Intermediary New England Energy, LLC', '', '125 Cambridgepark Dr, Cambridge, MA 02140', '', '07-10-08  \n11/28/2007\n9/8/2011', '']


我实现了一个简单的正则表达式来删除此:

tds = [re.sub('\n+|\s+',' ',td.text) for td in tr.select('td')]


结果(也许不是最好的例子,但是是说明性的):

['', '', '', '', 'WFM Intermediary New England Energy, LLC', '', '125 Cambridgepark Dr, Cambridge, MA 02140', '', '07-10-08 11/28/2007 9/8/2011', '']


正则表达式:

python - 从&lt;a&gt;标记内的网页中提取公司名称-LMLPHP

最后(您到现在为止吗?),我们想为数据框添加一些标题。我们可以通过\n使用页面中的内容,并确保包含自定义的extend标头。

headers = ['Status']
headers.extend([th.text for th in soup.select('th[align]')])


python - 从&lt;a&gt;标记内的网页中提取公司名称-LMLPHP



Py:

from bs4 import BeautifulSoup as bs
import requests, re
import pandas as pd

r = requests.get('http://www.dpuc.state.ct.us/electric.nsf/$FormByElectricApplicantsView?OpenForm&Start=1&Count=1000&ExpandView')
soup = bs(r.content, 'lxml')
final = []
headers = ['Status']
headers.extend([th.text for th in soup.select('th[align]')])

for tr in soup.select('tr:has(td:nth-of-type(1) font:contains(Supplier)) ~ tr:not(:has(td:nth-of-type(1) font:contains(Aggregator)), :has(td:nth-of-type(1) font:contains(Aggregator)) ~ tr)'):
    node = tr.select_one('td:nth-of-type(2) font')
    if node is not None:
        status = node.text
    else:
        row = [status]
        tds = [re.sub('\n+|\s+',' ',td.text) for td in tr.select('td')]
        row.extend(tds)
        final.append(row)

df = pd.DataFrame(final)
df.drop([1,2,10], axis=1, inplace=True)
df.columns = headers
df.to_csv(r'C:\Users\User\Desktop\Public Utilities.csv', sep=',', encoding='utf-8-sig',index = False )




输出样本:

python - 从&lt;a&gt;标记内的网页中提取公司名称-LMLPHP



补充阅读:


Css selectors

09-27 08:17