Python - 提取带有ID的链接

2 投票
3 回答
3607 浏览
提问于 2025-04-17 10:44

我正在学习Python中的Beautiful Soup,想通过抓取数据来练习。我有一个HTML页面,格式是这样的……

span id listing-name-1
span class address
span preferredcontact="1"
a ID websiteLink1

span id listing-name-2
span class address
span preferredcontact="2"
a ID websiteLink2

span id listing-name-3
span class address
span preferredcontact="3"
a ID websiteLink3

这样一直到40个这样的条目。

我想获取这些类/ID里面的文本,并且希望它们的顺序和HTML页面上的顺序一致。

为了开始,我尝试了类似这样的代码来获取listing-name-1:

import urllib2
from BeautifulSoup import BeautifulSoup

page = urllib2.urlopen("http://www.yellowpages.com.au/search/listings?clue=architects&locationClue=New+South+Wales&x=45&y=12")

soup = BeautifulSoup(page)

soup.find(span,attrs={"id=listing-name-1"})

结果出现了远程主机强制关闭了一个现有的连接的错误。

我不知道该怎么解决这个问题。我需要帮助的有两个方面:

  1. 如何解决这个错误
  2. 我该如何从1到40遍历listing-name-1?我不想为所有40个Span ID都输入soup.find(span,attrs={"id=listing-name-1"})

谢谢!

3 个回答

1

你第一个问题看起来跟Python没什么关系。试着打印一下 page.read(),看看有没有输出。你也可以用浏览器打开这个页面,看看能不能加载出来。

至于你第二个问题,你可以给 findAll 传递一个正则表达式:

import re
import urllib2

from BeautifulSoup import BeautifulSoup

page = urllib2.urlopen("http://www.yellowpages.com.au/search/listings?clue=architects&locationClue=New+South+Wales&x=45&y=12")

soup = BeautifulSoup(page)

listing_names = re.compile('listing-name-[0-9]+')
listings = soup.findAll('span', id=listing_names)
print(listings)

上面的代码会打印出我电脑上的所有列表,所以你第一个问题肯定不是代码的问题。

1

你第二个问题的答案其实很简单:

import urllib2
from BeautifulSoup import BeautifulSoup

page = urllib2.urlopen("http://www.yellowpages.com.au/search/listings?clue=architects&locationClue=New+South+Wales&x=45&y=12")

soup = BeautifulSoup(page)

for num in range(1, 41):
    soup.find("span", attrs={"id": "listing-name-"+str(num)})
3

使用 lxml.html 时,你可以直接用一个网址来调用 parse,这样就不需要自己去调用 urllib 了。而且,和使用 findfindall 不同,你应该用 xpath,这样可以充分利用 xpath 的强大功能。如果你用 find 来调用下面的表达式,会出现 invalid predicate 的错误。

#!/usr/bin/env python

import lxml.html

url = "http://www.yellowpages.com.au/search/listings?clue=architects&locationClue=New+South+Wales&x=45&y=12"
tree = lxml.html.parse(url)
listings = tree.xpath("//span[contains(@id,'listing-name-')]/text()")
print listings

这样会输出结果,并保持顺序:

['Cape Cod Australia Pty Ltd',
'BHI',
'Fibrent Pty Ltd Building & Engineering Assessments',
 ...
'Archicentre']

关于你在我回答下的评论中提到的问题,你想要查找的是 <div class="listingInfoContainer">...</div>,这个部分包含了你想要的所有信息(比如名字、地址等)。然后你可以遍历那些符合条件的 div 元素,并使用 xpath 表达式来提取剩下的信息。需要注意的是,在这个例子中,我使用了 container.xpath('.//span'),这会从当前节点(也就是容器 div)开始搜索。如果你省略了 .,只用 //span,那搜索就会从树的顶部开始,这样会得到所有匹配的元素列表,这不是你想要的结果。

#!/usr/bin/env python

import lxml.html

url = "http://www.yellowpages.com.au/search/listings?clue=architects&locationClue=New+South+Wales&x=45&y=12"
tree = lxml.html.parse(url)
container = tree.xpath("//div[@class='listingInfoContainer']")
listings = []
for c in container:
    data = {}
    data['name'] = c.xpath('.//span[contains(@id,"listing")]/text()')
    data['address'] = c.xpath('.//span[@class="address"]/text()')
    listings.append(data)

print listings

这会输出:

[{'name': ['Cape Cod Australia Pty Ltd'], 
  'address': ['4th Floor 410 Church St, North Parramatta NSW 2151']}, 
 {'name': ['BHI'], 
  'address': ['Suite 5, 65 Doody St, Alexandria NSW 2015']}, 
 {'name': ['Fibrent Pty Ltd Building & Engineering Assessments'], 
  'address': ["Suite 3B, Level 1, 72 O'Riordan St, Alexandria NSW 2015"]}, 
  ...
 {'name': ['Archicentre'], 
  'address': ['\n                                         Level 3, 60 Collins St\n                                         ',
              '\n                                         Melbourne VIC 3000\n                                    ']}]

结果是一个列表(同样保持你想要的顺序),里面是字典,每个字典都有 nameaddress 这两个键,每个键对应一个列表。这个最终的列表是通过 text() 返回的,它保留了原始 HTML 中的 \n 换行符,并把像 <br> 这样的标签转换成新的列表元素。比如,列表项 Archicentre 的原始 HTML 表示是:

<span class="address">
     Level 3, 60 Collins St
     <br/>
     Melbourne VIC 3000
</span>

撰写回答