如何使用 Python 使用动态生成的 URL 抓取页面?

Posted

技术标签:

【中文标题】如何使用 Python 使用动态生成的 URL 抓取页面?【英文标题】:How do I scrape pages with dynamically generated URLs using Python? 【发布时间】:2014-06-11 17:34:15 【问题描述】:

我正在尝试抓取 http://www.dailyfinance.com/quote/NYSE/international-business-machines/IBM/financial-ratios,但传统的 url 字符串构建技术不起作用,因为“full-company-name-is-inserted-in-the-path”字符串。确切的“公司全名”事先并不知道。只有公司符号“IBM”是已知的。

基本上,我抓取的方式是循环遍历公司符号数组并构建 url 字符串,然后将其发送到 urllib2.urlopen(url)。但在这种情况下,这是做不到的。

例如,CSCO 字符串是

http://www.dailyfinance.com/quote/NASDAQ/cisco-systems-inc/CSCO/financial-ratios

另一个示例 url 字符串是 AAPL:

http://www.dailyfinance.com/quote/NASDAQ/apple/AAPL/financial-ratios

所以为了获取url,我不得不在主页的输入框中搜索符号:

http://www.dailyfinance.com/

我注意到,当我在 Firefox Web 开发人员网络选项卡中键入“CSCO”并检查搜索输入 (http://www.dailyfinance.com/quote/NASDAQ/apple/AAPL/financial-ratios) 时,我注意到 get 请求正在发送到

http://j.foolcdn.com/tmf/predictivesearch?callback=_predictiveSearch_csco&term=csco&domain=dailyfinance.com

并且引用者实际上给出了我想要捕获的路径

Host: j.foolcdn.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:28.0) Gecko/20100101 Firefox/28.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://www.dailyfinance.com/quote/NASDAQ/cisco-systems-inc/CSCO/financial-ratios?source=itxwebtxt0000007
Connection: keep-alive

抱歉,解释太长了。所以问题是如何提取Referer中的url?如果这是不可能的,我应该如何解决这个问题?还有其他方法吗?

非常感谢您的帮助。

【问题讨论】:

+1:非常问得好,对我个人而言。 这方面有更新吗?您是否看到我的回答中如何正确处理Referer? 【参考方案1】:

我喜欢这个问题。正因为如此,我会给出一个非常彻底的答案。为此,我将使用我最喜欢的 Requests 库和 BeautifulSoup4。如果您真的想使用,移植到 Mechanize 取决于您。不过,请求会为您省去很多麻烦。


首先,您可能正在寻找 POST 请求。但是,如果搜索功能您立即带到您正在寻找的页面,则通常不需要 POST 请求。那么让我们检查一下,好吗?

当我登陆基本 URL http://www.dailyfinance.com/ 时,我可以通过 Firebug 或 Chrome 的检查工具进行简单检查,当我在搜索栏上输入 CSCO 或 AAPL 并启用“跳转”时,有一个 @987654326 @状态码。这是什么意思?

简单来说,我被调动到了某个地方。此 GET 请求的 URL 如下:

http://www.dailyfinance.com/quote/jump?exchange-input=&ticker-input=CSCO

现在,我们通过简单的 URL 操作来测试它是否适用于 AAPL。

import requests as rq

apl_tick = "AAPL"
url = "http://www.dailyfinance.com/quote/jump?exchange-input=&ticker-input="
r = rq.get(url + apl_tick)
print r.url

上面给出了以下结果:

http://www.dailyfinance.com/quote/nasdaq/apple/aapl
[Finished in 2.3s]

看看响应的 URL 是如何变化的?让我们通过将以下内容附加到上述代码中来查找/financial-ratios 页面,从而进一步进行 URL 操作:

new_url = r.url + "/financial-ratios"
p = rq.get(new_url)
print p.url

运行时,结果如下:

http://www.dailyfinance.com/quote/nasdaq/apple/aapl
http://www.dailyfinance.com/quote/nasdaq/apple/aapl/financial-ratios
[Finished in 6.0s]

现在我们走在了正确的轨道上。我现在将尝试使用 BeautifulSoup 解析数据。我的完整代码如下:

from bs4 import BeautifulSoup as bsoup
import requests as rq

apl_tick = "AAPL"
url = "http://www.dailyfinance.com/quote/jump?exchange-input=&ticker-input="
r = rq.get(url + apl_tick)
new_url = r.url + "/financial-ratios"
p = rq.get(new_url)

soup = bsoup(p.content)
div = soup.find("div", id="clear").table
rows = table.find_all("tr")
for row in rows:
    print row

然后我尝试运行此代码,但遇到以下回溯错误:

  File "C:\Users\nanashi\Desktop\test.py", line 13, in <module>
    div = soup.find("div", id="clear").table
AttributeError: 'NoneType' object has no attribute 'table'

值得注意的是'NoneType' object...这一行。这意味着我们的目标div 不存在! Egads,但为什么我看到以下内容?!

只能有一种解释:表格是动态加载的!大鼠。让我们看看是否可以找到该表的另一个来源。我研究了页面,发现底部有滚动条。这可能意味着表格是在框架内加载的,或者是完全从另一个源直接加载并放置在页面中的div 中。

我刷新页面并再次查看 GET 请求。宾果游戏,我发现了一些看起来有点前途的东西:

第三方来源网址,看,使用股票代码很容易操作!让我们尝试将其加载到新选项卡中。这是我们得到的:

哇!我们现在有了非常准确的数据来源。最后一个障碍是当我们尝试使用这个字符串提取 CSCO 数据时它会起作用(记住我们去了 CSCO -> AAPL,现在又回到了 CSCO,所以你不会感到困惑)。让我们清理一下字符串,完全抛弃www.dailyfinance.com 的角色。我们的新网址如下:

http://www.motleyfool.idmanagedsolutions.com/stocks/financial_ratios.idms?SYMBOL_US=AAPL

让我们尝试在最终的爬虫中使用它!

from bs4 import BeautifulSoup as bsoup
import requests as rq

csco_tick = "CSCO"
url = "http://www.motleyfool.idmanagedsolutions.com/stocks/financial_ratios.idms?SYMBOL_US="
new_url = url + csco_tick

r = rq.get(new_url)
soup = bsoup(r.content)

table = soup.find("div", id="clear").table
rows = table.find_all("tr")
for row in rows:
    print row.get_text()

我们对 CSCO 财务比率数据的原始结果如下:

Company
Industry


Valuation Ratios


P/E Ratio (TTM)
15.40
14.80


P/E High - Last 5 Yrs 
24.00
28.90


P/E Low - Last 5 Yrs
8.40
12.10


Beta
1.37
1.50


Price to Sales (TTM)
2.51
2.59


Price to Book (MRQ)
2.14
2.17


Price to Tangible Book (MRQ)
4.25
3.83


Price to Cash Flow (TTM)
11.40
11.60


Price to Free Cash Flow (TTM)
28.20
60.20


Dividends


Dividend Yield (%)
3.30
2.50


Dividend Yield - 5 Yr Avg (%)
N.A.
1.20


Dividend 5 Yr Growth Rate (%)
N.A.
144.07


Payout Ratio (TTM)
45.00
32.00


Sales (MRQ) vs Qtr 1 Yr Ago (%)
-7.80
-3.70


Sales (TTM) vs TTM 1 Yr Ago (%)
5.50
5.60


Growth Rates (%)


Sales - 5 Yr Growth Rate (%)
5.51
5.12


EPS (MRQ) vs Qtr 1 Yr Ago (%)
-54.50
-51.90


EPS (TTM) vs TTM 1 Yr Ago (%)
-54.50
-51.90


EPS - 5 Yr Growth Rate (%)
8.91
9.04


Capital Spending - 5 Yr Growth Rate (%)
20.30
20.94


Financial Strength


Quick Ratio (MRQ)
2.40
2.70


Current Ratio (MRQ)
2.60
2.90


LT Debt to Equity (MRQ)
0.22
0.20


Total Debt to Equity (MRQ)
0.31
0.25


Interest Coverage (TTM)
18.90
19.10


Profitability Ratios (%)


Gross Margin (TTM)
63.20
62.50


Gross Margin - 5 Yr Avg
66.30
64.00


EBITD Margin (TTM)
26.20
25.00


EBITD - 5 Yr Avg
28.82
0.00


Pre-Tax Margin (TTM)
21.10
20.00


Pre-Tax Margin - 5 Yr Avg
21.60
18.80


Management Effectiveness (%)


Net Profit Margin (TTM)
17.10
17.65


Net Profit Margin - 5 Yr Avg
17.90
15.40


Return on Assets (TTM)
8.30
8.90


Return on Assets - 5 Yr Avg
8.90
8.00


Return on Investment (TTM)
11.90
12.30


Return on Investment - 5 Yr Avg
12.50
10.90


Efficiency


Revenue/Employee (TTM)
637,890.00
556,027.00


Net Income/Employee (TTM)
108,902.00
98,118.00


Receivable Turnover (TTM)
5.70
5.80


Inventory Turnover (TTM)
11.30
9.70


Asset Turnover (TTM)
0.50
0.50

[Finished in 2.0s]

清理数据由您决定。


从这次抓取中学到的一个很好的教训是,并非所有数据都单独包含在一个页面中。很高兴看到它来自另一个静态站点。如果它是通过 javascript 或 AJAX 调用等产生的,我们的方法可能会遇到一些困难。

希望你从中学到了一些东西。让我们知道这是否有帮助并祝您好运。

【讨论】:

哇,你是一个网络抓取狂人(从某种意义上说)! :) 今天没有更多的赞成票了 - 明天就会到达并 +1。 @alecxe:伙计,你不知道我从你身上学到了多少。来自你,这是一个巨大的赞美。 ;) 谢谢! 谢谢!我们在这里互相学习。如此详细的答案确实使网络抓取世界变得更好。希望在相关标签中听到您的更多消息。 +1 - 超出 OP 要求!我从中学到了很多。 @cdhagmann:如果我没有看到底部的滚动条,真的想阻止它并让 OP 弄清楚。我真的不喜欢动态生成的任何东西,因为它们会带来很多问题,但我很高兴我再次刷新它以捕捉第三方调用。谢谢!【参考方案2】:

不回答您的具体问题,但可以解决您的问题。

http://www.dailyfinance.com/quotes/Company Symbol/Stock Exchange

例子:

http://www.dailyfinance.com/quotes/AAPL/NAS

http://www.dailyfinance.com/quotes/IBM/NYSE

http://www.dailyfinance.com/quotes/CSCO/NAS

要进入财务比率页面,您可以使用以下内容:

import urllib2

def financial_ratio_url(symbol, stock_exchange):
    starturl  = 'http://www.dailyfinance.com/quotes/'
    starturl += '/'.join([symbol, stock_exchange])
    req = urllib2.Request(starturl)
    res = urllib2.urlopen(starturl)
    return '/'.join([res.geturl(),'financial-ratios'])

例子:

financial_ratio_url('AAPL', 'NAS')
'http://www.dailyfinance.com/quote/nasdaq/apple/aapl/financial-ratios'

【讨论】:

感谢您的回答。我发现它很有用而且很有教育意义。

以上是关于如何使用 Python 使用动态生成的 URL 抓取页面?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 NextJS+React 中使用 api 生成的内容创建用户可自定义的动态 url?

(转)秒杀系统中如何动态生成下单随机URL

使用 Struts2 在创建博客条目时生成动态 URL

如何使用 Python 检索动态 html 内容的值

如何将动态生成的URL重定向到另一个选项卡?

python爬虫获取浏览器payload?