Python에서 Slack에 기압 정보 (BeautifulSoup에서 취득)를 그래프 첨부로 보내고 싶다 (+Google Spread Sheet로 관리하고 싶다)

우선 Github



소스 코드(Github)

배경



저기압 혹은 기압이 급강하할 때에는, 이하의 증상이 나타나는 사람이 많다고 합니다.
  • 신체적 : 나사, 졸음, 두통
  • 영적 : 우울함과 부정적

  • 내 타임 라인을 보면 기압이 내려 왔을 때 경고를 내주는 사람도 있습니다.
    기사도 꽤 있습니다.

    과학적 근거가 있는지 몰라

    일단 내 컨디션과 기압이 관련되는지 알아보고 싶어졌습니다.

    목적


  • 기압 변화의 그래프와 통지를 보면서 자신의 컨디션과 주관적으로 파악하는 것
  • 기분 변화 기록과 비교하여 상관 관계가 있는지 수학? 확인하기

  • 해결 방법 · 이번 일


  • 최근 기압 취득 <= 이번 기사
  • 기압을 Slack 통지 <= 다음 기사
  • Google Spread Sheet에 저장 <= 추가로 다음

  • ( tree 명령은 win에서도 사용할 수 있네요)
    root/
    │  .env
    │  .env.sample
    │  .gitignore
    │  app.py
    │  ---.json
    │  README.md
    │  requirements.txt
    │
    ├─api_packages/
    │  │  googleItem.py
    │  │  slackItem.py
    │  │  __init__.py
    │
    ├─data_packages/
    │  │  fetchHtml.py
    │  │  generateGraph.py
    │  │  __init__.py
    │
    ├─imgs/
    
    python=3.8
    
    pip install beautifulsoup4
    pip install requests
    pip install numpy
    pip install matplotlib
    pip install python-dotenv
    pip install gspread
    pip install oauth2client
    

    기압 획득



    API로 취득하는 것도 생각했습니다만, 기상청이 공개하고 있는 도쿄의 데이터를 사용하게 하기로 했습니다.
    htps //w w. j. . jp / jp / 아메다 s_h / 토다 y-44132. HTML

    그냥 BeautifulSoup 공부도하고 싶었기 때문에 그것도 겸하고 있습니다.

    fetchHtml.py
    import requests
    from bs4 import BeautifulSoup
    import datetime
    
    import re
    
    class HtmlFetcher:
      url = None
    
      def __init__(self, url):
        self.url = url # 可変にしようとしたが、止めた…
    
      def fetch_pressure_from_jma(self, search_time = datetime.datetime.now()): # デフォルト引数を取っていますがこれやるとだめ
        url = "調べたいURL"
        try:
          res = requests.get(url)
          soup = BeautifulSoup(res.text, 'html.parser')
          trs = soup.select("div#div_table > table > tr")
          list_time_pressure = []
          for tr in trs:
            tds = tr.select("td")
            if tds[0].get_text().isdigit() and tds[8].get_text().replace('\xa0', ''):
              list_time_pressure.append(
                {
                  'time': int(tds[0].get_text()),
                  'pressure': tds[8].get_text()
                }
              )
          if len(list_time_pressure) == 0:
            return None
          else:
            date_place_title = soup.select("table#tbl_title td.td_title")[0].get_text()
            year = re.search(r'\d{4}年', date_place_title).group()[0:4]
            month = re.search(r'\d{2}月', date_place_title).group()[0:2]
            day = re.search(r'\d{2}日', date_place_title).group()[0:2]
            place = re.search(r'\s+\D+\Z', date_place_title).group()[1:]
            result = {}
    
            # if search_time:
              # 指定した時刻に最も近いデータを取得したかった…
              # sorted_time_pressures = sorted(list_time_pressure, key=lambda x: abs(int(search_time.hour - x['time'])))
            # もっとも時間が最近のものを取得
            sorted_time_pressures = sorted(list_time_pressure, key=lambda x:-x['time'])
            result = {
              'data': sorted_time_pressures,
              'info': {'day': day, 'month': month, 'year': year, 'place': place}
            }
            return result
        except Exception as e:
          print(e)
          return False
    

    어쩐지 분위기에서 읽을 수 있을까 생각합니다만
    trs = soup.select("div#div_table > table > tr")
    
    jQuery 이나 SCSS 를 익숙한 사람이라면 비교적 직관적인 기법이라고 생각합니다.
    위의 페이지는 단순한 구성이었지만, 반대로 Class 가 별로 없거나 해서 깊은 취득 방법을 해야 했습니다.


    해당 페이지에서 2020년 03월 01일에 인용
    if tds[0].get_text().isdigit() and tds[8].get_text().replace('\xa0', ''):
    

    여기서 그 행이 기압을 표시하고 있는지를 판정하고 있습니다.


    시간
    기온
    강수량
    ...


    시간

    mm
    ...

    1
    8.5
    0.5
    ...


    「시간」의 행과 「시」의 행은 기압 데이터가 없습니다.
    그래서 정수로 변환할 수 있는 데이터라면 기압이 9번째는 열에는 기압이 들어가 있다고 합니다.

    또 기압이 미기입이라면 취득해도 어쩔 수 없기 때문에 나중의 조건을 넣고 있습니다.
    수수께끼의 문자가 들어 있기 때문에 대체했습니다.

    여기 제대로 움직이고 있는지 기사 쓰고 있으면 불안해져 왔습니다.
    list_time_pressure = []
    

    예를 들어 오전 1시에 얻으면 이 목록은 길이 1이라는 느낌이 됩니다.
    date_place_title = soup.select("table#tbl_title td.td_title")[0].get_text()
    

    최초의 지명과 날짜가 들어가는 무렵의 정보를 취득하고 있습니다.
    td_title 라고 Class명을 붙이는 것 같습니다.

    이것을 정규 표현을 사용해 분할해 갑니다.
    # 最初の整数4桁は年
    # 大化の改新があった645年や未来20000年のデータを取得できないんですが、まあ…
    year = re.search(r'\d{4}年', date_place_title).group()[0:4]
    month = re.search(r'\d{2}月', date_place_title).group()[0:2]
    day = re.search(r'\d{2}日', date_place_title).group()[0:2]
    # 一番最後は土地名のようです
    place = re.search(r'\s+\D+\Z', date_place_title).group()[1:]
    
    sorted(list_time_pressure, key=lambda x:-x['time'])
    

    가장 시간이 가까운 순서로 정렬하고 있습니다.
    날짜 변경하면 대응할 수 없을까~라고 생각하면, 전날의 데이터는 날짜가 바뀐 타이밍에 리셋 되는 것은 아니었기 때문에, 이것으로 말하고 있습니다.

    여담



    여기서 피곤해서 기사를 나누기로 결정했습니다.

    좋은 웹페이지 즐겨찾기