我正在尝试使用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列表中,那将不胜感激,并允许我通过示例进行学习。
答案 0 :(得分:1)
使用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')
让我们用一些图像来说明下一步...
我们拥有的可能是令人困惑的是很多嵌套表,但是我们可以从考虑感兴趣的“顶级”表开始:
我们可以从中获取(将鼠标悬停在实际html中该图像的不同trs
上,然后观察页面上突出显示的内容。您可以在浏览器搜索框中输入p:nth-child(4) > table
隔离此表):
是的,实际可见的内容是嵌套的,但我们知道所有感兴趣的内容都在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)')
收集有个子trs
节点包含 {{1}的“顶级” tr
的同级font
},Supplier
,然后删除“最高”级别tr:has(td:nth-of-type(1) font:contains(Supplier))
包含的tr
及其兄弟姐妹。由于html中包含一长串Aggregator
,因此您只想过滤掉感兴趣的部分之后的内容。
看第一部分(我使用trs
而不是select_one
来演示匹配的第一个节点,而不是当我们添加{{ 3}}-稍后再介绍):
select
运行上面的命令可获得:
将此与页面比较:
您会看到我们如何找到一种从顶部(尽管嵌套)包含soup.select_one('tr:has(td:nth-of-type(1) font:contains(Supplier))')
标签开始选择顶层trs
的方法。但是,我们知道顶层是平坦的,因此我们需要删除Supplier
标签中的所有内容(包括其中)。因此,我们向该通用同级组合器添加了伪类。简而言之,我们说获得Aggregator
trs
现在,我们有了感兴趣的行的子集(绿色矩形)。我们:not
遍历这些,每次找到与for loop
匹配的节点,我们都将'td:nth-of-type(2) font'
设置为找到的值,例如status
.....
此行:
Approved, Pending
正在检查当前if node is not None:
是否包含'状态'子节点,例如tr
(我将其命名为Approved/Pending
进行输出),并相应地为此类别设置以后的行标识符。
如果status
没有此子节点,那么我们知道它是容纳tr
的其他trs
之一,并带有附加的输出信息列,例如:>
由于嵌套级别的原因,有一些空的tds
包括我们不想要的。这些我们稍后会使用熊猫删除:
tds
由于我们希望在一个列表df.drop([1,2,10], axis=1, inplace=True)
中使用status
标签和所有tds
,所以我使用row
扩展了第一个列表以包括第二个列表。我相信这比extend
更快,但希望您对此感到满意。
因此,我们可以从例如:
insert
和
['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', '']
的列表中。因此,您有一个列表列表(每行)。
您可以将此列表传递到来生成一个准备好使用pandas.DataFrame方法进行csv导出的数据框。您可以使用final
参数来指定标题。
生成行时:
columns
我们偶尔发现了额外的空白和tds = [td.text for td in tr.select('td')]
(换行符),例如
\n
我实现了一个简单的正则表达式来删除它:
['', '', '', '', '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', '']
使用该页面中的内容,并确保我们包含自定义的extend
标头。
status
Py:
headers = ['Status']
headers.extend([th.text for th in soup.select('th[align]')])
输出示例:
其他阅读内容:
答案 1 :(得分:0)
company_list = [];
for company in soup.find_all('a'):
company_list.append(company.string);
您可以在BeautifulSoup文档中找到有关此内容的更多详细信息。您可能需要修改此代码,以通过循环中的条件过滤掉特定的标签,但我认为这应该非常简单。
更具体地说,对于您的特定示例,您可以通过正则表达式或任何您喜欢的选项检查company.get('href')
与“ .nsf”的匹配项。