文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

程序员三大美德之一:快速有效检索网页数据的“懒惰”程序员指南

2024-12-11 16:47

关注

[[336534]]

比尔盖茨的观点是:我选择让懒惰的人完成艰巨的任务,因为他可以找到完成任务的捷径。

网页抓取或许是一个相当简单的编程问题:在文档的源代码中搜索唯一标识符,提取相关数据,但我认为存在一个更“懒惰”的解决方案——更简单,更快,可以生成更多数据。

雅虎财经是财务数据做得最好的网站之一,这也让它成为金融爱好者进行网页抓取的主要目标。几乎每天都有关于StackOverflow的问题,抓取数据的人参考了雅虎财经的某种数据检索(通常是通过网络抓取)。

网页抓取问题1

网页抓取者尝试查找Facebook当前的股票价格。代码如下:

  1. import requests 
  2.         from bs4 importBeautifulSoup 
  3.              defparsePrice(): 
  4.           r = requests.get("https://finance.yahoo.com/quote/FB?p=FB"
  5.           soup =BeautifulSoup(r.text, "lxml"
  6.           price = soup.find( div , { class : My(6px) Pos(r)smartphone_Mt(6px) }).find( span ).text 
  7.           print(f the current price: {price} ) 

该代码输出如下:

  1. the current price: 216.08 

使用简单的网页抓取解决方案非常简单,但这还不够“懒惰”,让我们看下一个。

网页抓取问题2

网页抓取者正在尝试从统计标签中查找有关股票的企业价值和空头股票数量的数据。他的问题实际上是检索可能存在或不存在的嵌套字典值,但是在检索数据上,他似乎已经找到了更好的解决方法。

  1. import requests, re, json, pprint 
  2.              p = re.compile(r root.App.main =(.*); ) 
  3.         tickers = [ AGL.AX ] 
  4.         results = {} 
  5.              with requests.Session() as s: 
  6.                  for ticker in tickers: 
  7.                 r = s.get( https://finance.yahoo.com/quote/{}/key-statistics?p={} .format(ticker,ticker)) 
  8.                 data = json.loads(p.findall(r.text)[0]) 
  9.                 key_stats = data[ context ][ dispatcher ][ stores ][ QuoteSummaryStore ] 
  10.                 print(key_stats) 
  11.                 res = { 
  12.                          Enterprise Value  : key_stats[ defaultKeyStatistics ][ enterpriseValue ][ fmt ] 
  13.                         , Shares_Short  : key_stats[ defaultKeyStatistics ][ sharesShort ].get( longFmt ,  N/A ) 
  14.                 } 
  15.                 results[ticker] = res 
  16.              print(results) 

看第3行:网页抓取者能够在javascript的变量内找到他要查找的数据:

  1. root.App.main = {.... }; 

在那里,只需访问字典中适当的嵌套键,即可轻松检索数据。但是,确实还有更“懒惰”的办法。

“懒惰”的解决方案1

  1. import requests 
  2.              r = requests.get("https://query2.finance.yahoo.com/v10/finance/quoteSummary/FB?modules=price"
  3.        data = r.json() 
  4.        print(data) 
  5.        print(f"the currentprice: {data[ quoteSummary ][ result ][0][ price ][ regularMarketPrice ][ raw ]}"

看看第三行的URL,输出如下:

  1.      quoteSummary : { 
  2.          error : None, 
  3.          result : [{ 
  4.              price : { 
  5.                 averageDailyVolume10Day : {}, 
  6.                 averageDailyVolume3Month : {}, 
  7.                  circulatingSupply : {}, 
  8.                  currency :  USD , 
  9.                  currencySymbol :  $ , 
  10.                  exchange :  NMS , 
  11.                  exchangeDataDelayedBy :0, 
  12.                  exchangeName : NasdaqGS , 
  13.                  fromCurrency : None, 
  14.                  lastMarket : None, 
  15.                  longName :  Facebook,Inc. , 
  16.                  marketCap : { 
  17.                      fmt :  698.42B , 
  18.                      longFmt : 698,423,836,672.00 , 
  19.                      raw : 698423836672 
  20.                 }, 
  21.                  marketState :  REGULAR , 
  22.                  maxAge : 1, 
  23.                  openInterest : {}, 
  24.                  postMarketChange : {}, 
  25.                  postMarketPrice : {}, 
  26.                  preMarketChange : { 
  27.                      fmt :  -0.90 , 
  28.                      raw : -0.899994 
  29.                 }, 
  30.                  preMarketChangePercent :{ 
  31.                      fmt :  -0.37% , 
  32.                      raw : -0.00368096 
  33.                 }, 
  34.                  preMarketPrice : { 
  35.                      fmt :  243.60 , 
  36.                      raw : 243.6 
  37.                 }, 
  38.                  preMarketSource : FREE_REALTIME , 
  39.                  preMarketTime :1594387780, 
  40.                  priceHint : { 
  41.                      fmt :  2 , 
  42.                      longFmt :  2 , 
  43.                      raw : 2 
  44.                 }, 
  45.                  quoteSourceName : Nasdaq Real Time   
  46.                  Price , 
  47.                  quoteType :  EQUITY , 
  48.                  regularMarketChange : { 
  49.                      fmt :  0.30 , 
  50.                      raw : 0.30160522 
  51.                 }, 
  52.                 regularMarketChangePercent : { 
  53.                      fmt :  0.12% , 
  54.                      raw : 0.0012335592 
  55.                 }, 
  56.                  regularMarketDayHigh : { 
  57.                      fmt :  245.49 , 
  58.                      raw : 245.49 
  59.                 }, 
  60.                  regularMarketDayLow : { 
  61.                      fmt :  239.32 , 
  62.                      raw : 239.32 
  63.                 }, 
  64.                  regularMarketOpen : { 
  65.                      fmt :  243.68 , 
  66.                      raw : 243.685 
  67.                 }, 
  68.                 regularMarketPreviousClose : { 
  69.                      fmt :  244.50 , 
  70.                      raw : 244.5 
  71.                 }, 
  72.                  regularMarketPrice : { 
  73.                      fmt :  244.80 , 
  74.                      raw : 244.8016 
  75.                 }, 
  76.                  regularMarketSource : FREE_REALTIME , 
  77.                  regularMarketTime :1594410026, 
  78.                  regularMarketVolume : { 
  79.                      fmt :  19.46M , 
  80.                      longFmt :  19,456,621.00 , 
  81.                      raw : 19456621 
  82.                 }, 
  83.                  shortName :  Facebook,Inc. , 
  84.                  strikePrice : {}, 
  85.                  symbol :  FB , 
  86.                  toCurrency : None, 
  87.                  underlyingSymbol : None, 
  88.                  volume24Hr : {}, 
  89.                  volumeAllCurrencies : {} 
  90.             } 
  91.         }] 
  92.     } 
  93. }the current price: 241.63 

“懒惰”的解决方案2

  1. import requests 
  2.              r = requests.get("https://query2.finance.yahoo.com/v10/finance/quoteSummary/AGL.AX?modules=defaultKeyStatistics"
  3.       data = r.json() 
  4.       print(data) 
  5.       print({ 
  6.            AGL.AX : { 
  7.                Enterprise Value : data[ quoteSummary ][ result ][0][ defaultKeyStatistics ][ enterpriseValue ][ fmt ], 
  8.                Shares Short : data[ quoteSummary ][ result ][0][ defaultKeyStatistics ][ sharesShort ].get( longFmt ,  N/A ) 
  9.           } 
  10.       }) 

再次看一下第三行的URL,输出如下:

  1.      quoteSummary : { 
  2.          result : [{ 
  3.              defaultKeyStatistics : { 
  4.                  maxAge : 1, 
  5.                  priceHint : { 
  6.                      raw : 2, 
  7.                      fmt :  2 , 
  8.                      longFmt :  2  
  9.                 }, 
  10.                  enterpriseValue : { 
  11.                      raw : 13677747200, 
  12.                      fmt :  13.68B , 
  13.                      longFmt : 13,677,747,200  
  14.                 }, 
  15.                  forwardPE : {}, 
  16.                  profitMargins : { 
  17.                      raw : 0.07095, 
  18.                      fmt :  7.10%  
  19.                 }, 
  20.                  floatShares : { 
  21.                      raw : 637754149, 
  22.                      fmt :  637.75M , 
  23.                      longFmt : 637,754,149  
  24.                 }, 
  25.                  sharesOutstanding : { 
  26.                      raw : 639003008, 
  27.                      fmt :  639M , 
  28.                      longFmt : 639,003,008  
  29.                 }, 
  30.                  sharesShort : {}, 
  31.                  sharesShortPriorMonth :{}, 
  32.                  sharesShortPreviousMonthDate :{}, 
  33.                  dateShortInterest : {}, 
  34.                  sharesPercentSharesOut : {}, 
  35.                  heldPercentInsiders : { 
  36.                      raw : 0.0025499999, 
  37.                      fmt :  0.25%  
  38.                 }, 
  39.                  heldPercentInstitutions : { 
  40.                      raw : 0.31033, 
  41.                      fmt :  31.03%  
  42.                 }, 
  43.                  shortRatio : {}, 
  44.                  shortPercentOfFloat :{}, 
  45.                  beta : { 
  46.                      raw : 0.365116, 
  47.                      fmt :  0.37  
  48.                 }, 
  49.                  morningStarOverallRating :{}, 
  50.                  morningStarRiskRating :{}, 
  51.                  category : None, 
  52.                  bookValue : { 
  53.                      raw : 12.551, 
  54.                      fmt :  12.55  
  55.                 }, 
  56.                  priceToBook : { 
  57.                      raw : 1.3457094, 
  58.                      fmt :  1.35  
  59.                 }, 
  60.                 annualReportExpenseRatio : {}, 
  61.                  ytdReturn : {}, 
  62.                  beta3Year : {}, 
  63.                  totalAssets : {}, 
  64.                  yield : {}, 
  65.                  fundFamily : None, 
  66.                  fundInceptionDate : {}, 
  67.                  legalType : None, 
  68.                  threeYearAverageReturn :{}, 
  69.                  fiveYearAverageReturn :{}, 
  70.                  priceToSalesTrailing12Months :{}, 
  71.                  lastFiscalYearEnd : { 
  72.                      raw : 1561852800, 
  73.                      fmt :  2019-06-30  
  74.                 }, 
  75.                  nextFiscalYearEnd : { 
  76.                      raw : 1625011200, 
  77.                      fmt :  2021-06-30  
  78.                 }, 
  79.                  mostRecentQuarter : { 
  80.                      raw : 1577750400, 
  81.                      fmt :  2019-12-31  
  82.                 }, 
  83.                 earningsQuarterlyGrowth : { 
  84.                      raw : 0.114, 
  85.                      fmt :  11.40%  
  86.                 }, 
  87.                  revenueQuarterlyGrowth :{}, 
  88.                  netIncomeToCommon : { 
  89.                      raw : 938000000, 
  90.                      fmt :  938M , 
  91.                      longFmt : 938,000,000  
  92.                 }, 
  93.                  trailingEps : { 
  94.                      raw : 1.434, 
  95.                      fmt :  1.43  
  96.                 }, 
  97.                  forwardEps : {}, 
  98.                  pegRatio : {}, 
  99.                  lastSplitFactor : None, 
  100.                  lastSplitDate : {}, 
  101.                  enterpriseToRevenue : { 
  102.                      raw : 1.035, 
  103.                      fmt :  1.03  
  104.                 }, 
  105.                  enterpriseToEbitda : { 
  106.                      raw : 6.701, 
  107.                      fmt :  6.70  
  108.                 }, 
  109.                  52WeekChange : { 
  110.                      raw : -0.17621362, 
  111.                      fmt :  -17.62%  
  112.                 }, 
  113.                  SandP52WeekChange : { 
  114.                      raw : 0.045882702, 
  115.                      fmt :  4.59%  
  116.                 }, 
  117.                  lastDividendValue : {}, 
  118.                  lastCapGain : {}, 
  119.                  annualHoldingsTurnover :{} 
  120.             } 
  121.        }], 
  122.          error : None 
  123.     } 
  124. }{ AGL.AX : { Enterprise Value :  13.73B ,  Shares Short :  N/A }} 

“懒惰”的解决方案只是简单地将请求从使用前端URL更改为某种非官方的返回JSON数据的API端点。这个方案更简单,可以导出更多数据 ,那么它的速度呢?代码如下:

  1. import timeit 
  2.      import requests 
  3.      from bs4 importBeautifulSoup 
  4.      import json 
  5.      import re 
  6.              repeat =5 
  7.      number =5 
  8.              defweb_scrape_1(): 
  9.          r = requests.get(f https://finance.yahoo.com/quote/FB?p=FB ) 
  10.          soup =BeautifulSoup(r.text, "lxml"
  11.          price = soup.find( div , { class : My(6px) Pos(r)smartphone_Mt(6px) }).find( span ).text 
  12.          returnf the current price: {price}  
  13.              deflazy_1(): 
  14.          r = requests.get( https://query2.finance.yahoo.com/v10/finance/quoteSummary/FB?modules=price ) 
  15.          data = r.json() 
  16.          returnf"the currentprice: {data[ quoteSummary ][ result ][0][ price ][ regularMarketPrice ][ raw ]}" 
  17.      
  18.          defweb_scrape_2(): 
  19.          p = re.compile(r root.App.main = (.*); ) 
  20.          ticker = AGL.AX  
  21.          results = {} 
  22.          with requests.Session() as s: 
  23.              r = s.get( https://finance.yahoo.com/quote/{}/key-statistics?p={} .format(ticker,ticker)) 
  24.              data = json.loads(p.findall(r.text)[0]) 
  25.              key_stats = data[ context ][ dispatcher ][ stores ][ QuoteSummaryStore ] 
  26.              res = { 
  27.                       Enterprise Value : key_stats[ defaultKeyStatistics ][ enterpriseValue ][ fmt ], 
  28.                       Shares Short : key_stats[ defaultKeyStatistics ][ sharesShort ].get( longFmt ,  N/A ) 
  29.              } 
  30.              results[ticker] = res 
  31.          return results 
  32.     
  33.          deflazy_2(): 
  34.          r = requests.get( https://query2.finance.yahoo.com/v10/finance/quoteSummary/AGL.AX?modules=defaultKeyStatistics ) 
  35.          data = r.json() 
  36.          return { 
  37.               AGL.AX : { 
  38.                   Enterprise Value : data[ quoteSummary ][ result ][0][ defaultKeyStatistics ][ enterpriseValue ][ fmt ], 
  39.                   Shares Short : data[ quoteSummary ][ result ][0][ defaultKeyStatistics ][ sharesShort ].get( longFmt ,  N/A ) 
  40.              } 
  41.          } 
  42.     
  43.          web_scraping_1_times = timeit.repeat( 
  44.           web_scrape_1() , 
  45.          setup= import requests; from bs4 import BeautifulSoup , 
  46.          globals=globals(), 
  47.          repeat=repeat, 
  48.          number=number) 
  49.      print(f web scraping #1min time is {min(web_scraping_1_times) / number} ) 
  50.              lazy_1_times = timeit.repeat( 
  51.           lazy_1() , 
  52.          setup= import requests , 
  53.          globals=globals(), 
  54.          repeat=repeat, 
  55.          number=number 
  56.      ) 
  57.      print(f lazy #1 min timeis {min(lazy_1_times) / number} ) 
  58.     
  59.          web_scraping_2_times = timeit.repeat( 
  60.           web_scrape_2() , 
  61.          setup= import requests, re, json , 
  62.          globals=globals(), 
  63.          repeat=repeat, 
  64.          number=number) 
  65.      print(f web scraping #2min time is {min(web_scraping_2_times) / number} ) 
  66.              lazy_2_times = timeit.repeat( 
  67.           lazy_2() , 
  68.          setup= import requests , 
  69.          globals=globals(), 
  70.          repeat=repeat, 
  71.          number=number 
  72.      ) 
  73.      print(f lazy #2 min timeis {min(lazy_2_times) / number} ) 
  1. web scraping #1 min time is 0.5678426799999997 
  2. lazy #1 min time is 0.11238783999999953 
  3. web scraping #2 min time is 0.3731000199999997 
  4. lazy #2 min time is 0.0864451399999993 

“懒惰”的替代方案比其网页抓取同类产品快4到5倍!

“偷懒”的过程

思考一下上面遇到的两个问题:原来的方案里,代码加载到页面后,我们尝试检索数据。“懒惰”的解决方案直接针对数据源,根本不理会前端页面。当你尝试从网站提取数据时,这是一个重要区别和一个很好的方法。

步骤1:检查XHR请求

XHR(XMLHttpRequest)对象是可用于Web浏览器脚本语言(例如JavaScript)的API,它将HTTP或HTTPS请求发送到Web服务器,并将服务器响应数据加载回脚本中。基本上,XHR允许客户端从URL检索数据,不必刷新整个网页。

笔者将使用Chrome进行以下演示,但是其他浏览器也具有类似的功能。

· 打开Chrome的开发者控制台。要在Google Chrome中打开开发者控制台,请打开浏览器窗口右上角的Chrome菜单,然后选择更多工具>开发者工具。也可以使用快捷键Option + ?+ J(适用于ios系统),或Shift + CTRL + J(适用于Windows / Linux)。

应注意,尽管有些请求包含“ AAPL”,得到的结果将相似但不相同。从调查这些开始,单击最左侧列中包含字符“ AAPL”的链接之一。

如果选择“预览”选项卡,将看到从服务器返回的数据。

 

好极了!看来我们找到了获取Apple OHLC数据的URL!

步骤2:搜寻

现在我们已经发现了一些通过浏览器发出的XHR请求。搜索javascript文件,查看是否可以找到更多信息。笔者发现与XHR请求相关的URL共同点是“ query1”和“ query2”。在开发者控制台的右上角,选择三个垂直点,然后在下拉框中选择“搜索”。

 

在搜索栏中搜索“ query2”:

选择第一个选项。将会弹出一个附加选项卡,其中包含找到“ query2”的位置。应该在这里注意到类似的内容:

网页抓取解决方案2提取的数据变量与该变量相同。控制台应提供“优质打印”变量的选项。你可以选择该选项,也可以将整行(上面的第11行)复制并粘贴到https://beautifier.io/。或者如果你使用vscode,下载美化扩展,它会做同样的事情。

正确格式化后,将整个代码粘贴到文本编辑器或类似的编辑器中,然后再次搜索“ query2”。搜索结果应该在 “ Service Plugin” 中。该部分包含雅虎财经用于在其页面中填充数据的URL。以下是该部分的内容:

  1. "tachyon.quoteSummary": { 
  2. "path""/v10/finance/quoteSummary/{symbol}"
  3. "timeout":6000, 
  4. "query": ["lang""region","corsDomain""crumb""modules",    "formatted"], 
  5. "responseField":"quoteSummary"
  6. "get": {"formatted"true
  7. }, 

以上是“懒惰”的解决方案中使用的URL。

“懒惰”人类发展的阶梯,适当偷懒,你会进入新世界。

本文转载自微信公众号「读芯术」,可以通过以下二维码关注。转载本文请联系读芯术公众号。

 

来源:读芯术 内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯