새로운 Twitter API를 사용하여 원격 개발자 직위를 발표하는 것에 대해 알 수 있는 다섯 가지

이 자습서의 코드는 에 제공됩니다.
이 문서에서는 원격 개발자의 작업 목록을 포함하는 트윗을 트위터 API로 실시간으로 가져오는 법을 배웠습니다.
이 후속 강좌에서는 지난 7일 동안 트위터에 게시된 원격 개발자 일자리의 공백에 대한 다섯 가지 필수 지식을 대답하기 위해 프로그램을 구축하는 방법을 배울 것입니다.
  • 지난 7일 동안 원격 개발자 일자리 공백에 대한 트윗은 모두 몇 건입니까?
  • 지난 7일 동안 잡스는 매주 어느 날 가장 많은 원격 개발자의 트위터를 보냈습니까?
  • 이러한 추문을 바탕으로 가장 인기 있는 프로그래밍 언어는 무엇입니까?
  • 어떤 추문이 전송, 선호, 회답과 인용을 통해 얻은 참여도가 가장 높습니까?
  • 이 트위터의 일들은 무엇처럼 보입니까?
  • 이 질문에 대답하기 위해서, 이 단점을 사용하는 프로그램을 구축할 것입니다. 이 단점은 응용 프로그램의 첫 번째 단점 중 하나입니다.이런 문제에 대한 답이 있으면 포부를 가진 구직자들은 그들의 구직을 최적화하고 다음 일자리를 찾도록 전략을 설계할 수 있다!
    설치 프로그램
    다음과 같이 시작해야 합니다.
  • 개발자 계정을 가지고 있어야 합니다.만약 네가 아직 없다면, 너는 할 수 있다.개발자 포털에 추가된 프로젝트의 개발자 응용 프로그램은 이벤트 키와 영패로 접근할 수 있습니다.
  • 노드js
  • Npm (Node와 함께 자동으로 설치됩니다.Npm 5.2 이상의 버전이 있는지 확인하십시오.)
  • Npx(npm 5.2 이상 포함)
  • 먼저 설치Node.js합니다.Node 웹 사이트의 Downloads 섹션을 보고 선택한 소스 코드나 설치 프로그램을 다운로드하십시오.또는 Mac 컴퓨터에서 실행할 경우 Node package 설치Brew package manager를 사용할 수 있습니다.
    터미널 창을 열고 create React app by usingnpx을 사용하여 React 응용 프로그램을 부트합니다.
    npx create-react-app remote-dev-jobs-analytics
    
    create react app가 실행된 후 새로 만든 원격 개발자 작업 분석 디렉터리로 전환하고 소프트웨어 패키지의 스크립트 블록을 대체합니다.가방에 다음 스크립트 블록을 포함하는 json입니다.json.이 줄들은 개발 또는 생산에서 클라이언트와 서버 백엔드 코드를 동시에 실행하는 명령 단축 방식을 제공할 것이다.
    cd remote-dev-jobs-analytics
    
    소포.json
      "scripts": {
        "start": "npm run development",
        "development": "NODE_ENV=development concurrently --kill-others \"npm run client\" \"npm run server\"",
        "client": "react-scripts start",
        "server": "nodemon server/server.js",
        "build": "react-scripts build",
        "test": "react-scripts test",
        "eject": "react-scripts eject"
      },
    
    [스크립트] 섹션을 업데이트하면 패키지가 삭제됩니다.제이슨은 지금 아래와 같아야 한다.
    다음에 src/하위 디렉터리의 모든 파일을 삭제합니다.
    rm src/*
    
    그리고 src/하위 디렉터리에 index라는 새 파일을 만듭니다.js.이 파일의 코드는 다음과 같다.
    import React from "react";
    import ReactDOM from "react-dom";
    import App from "./components/App";
    
    ReactDOM.render(<App />, document.querySelector("#root"));
    
    자격증
    가장 가까운 검색 노드에 연결하려면 프로그램의 불러오는 영패를 사용하여 인증해야 합니다.승재 영패를 사용하려면 다음과 같은 환경 변수를 설정해야 한다.만약 bash를 셸으로 사용한다면 터미널 창에서 다음 명령을 내릴 수 있습니다.왼쪽 괄호와 오른쪽 괄호를 포함하여 수표 소유자의 지폐로 바꾸십시오.
    export TWITTER_BEARER_TOKEN=<YOUR BEARER TOKEN HERE>
    
    서버측 코드
    먼저 Twitter API에 실제 요청을 보내는 노드 서버를 시작해야 합니다.이 노드 서버는 브라우저 기반 React 클라이언트와 Twitter API 사이의 에이전트로 사용됩니다.노드 서버에서 가장 가까운 검색 엔드포인트에 연결된 API 엔드포인트를 생성해야 합니다.반대로 React 클라이언트의 요청은 로컬 노드 서버로 프록시됩니다.
    더 나아가기 전에 cd를 프로젝트 루트 디렉터리로 이동하고 다음 의존항을 설치하십시오
    npm install concurrently express body-parser util request http path http-proxy-middleware axios react-router-dom react-twitter-embed react-chartjs-2
    
    다음에 프로젝트 루트 디렉터리에 '서버' 라는 새 하위 디렉터리를 만들고, 이 하위 디렉터리에 '서버.js' 라는 새 파일을 만듭니다.
    mkdir server
    touch server/server.js
    
    이 소스 코드 파일에는 가장 가까운 검색 엔드포인트에 연결되어 트윗을 받는 모든 백엔드 로직이 포함됩니다.서버의 컨텐트입니다.js 파일은 다음과 같습니다.
    서버.js
    const axios = require("axios");
    const express = require("express");
    const bodyParser = require("body-parser");
    const moment = require("moment");
    
    const app = express();
    let port = process.env.PORT || 3000;
    
    const BEARER_TOKEN = process.env.TWITTER_BEARER_TOKEN;
    
    app.use(bodyParser.json());
    app.use(bodyParser.urlencoded({ extended: true }));
    
    const searchURL = "https://api.twitter.com/2/tweets/search/recent";
    const query =
      '(developer OR software) remote (context:66.961961812492148736 OR context:66.850073441055133696) -is:retweet -"business developer"';
    const maxResults = 100;
    
    const requestConfig = {
      headers: {
        Authorization: `Bearer ${BEARER_TOKEN}`,
      },
      params: {
        max_results: maxResults,
        query: query,
        "tweet.fields": "context_annotations,created_at,public_metrics",
      },
    };
    
    const authMessage = {
      title: "Could not authenticate",
      detail: `Please make sure your bearer token is correct. 
          If using Glitch, remix this app and add it to the .env file`,
      type: "https://developer.twitter.com/en/docs/authentication",
    };
    
    app.get("/api/search/recent", async (req, res) => {
      if (!BEARER_TOKEN) {
        res.status(401).send(authMessage);
      }
    
      try {
        const response = await getSearchResults();
        res.send(response);
      } catch (e) {
        console.log(e);
      }
    });
    
    const getSearchResults = async (config = requestConfig) => {
      try {
        const response = await axios.get(searchURL, config);
        return response.data;
      } catch (e) {
        console.log(e);
      }
    };
    
    const getAllTweets = async () => {
      let response = await getSearchResults();
      let tweets = [];
    
      while (response.meta.next_token) {
        let config = {
          ...requestConfig,
          params: {
            ...requestConfig.params,
            next_token: response.meta.next_token,
          },
        };
    
        response = await getSearchResults(config);
        tweets = tweets.concat(response.data);
      }
    
      return tweets;
    };
    
    const getCount = async () => {
      let response = await getSearchResults();
      let resultCount = response.meta.result_count;
    
      while (response.meta.next_token) {
        let config = {
          ...requestConfig,
          params: {
            ...requestConfig.params,
            next_token: response.meta.next_token,
          },
        };
    
        response = await getSearchResults(config);
        resultCount = resultCount + response.meta.result_count;
      }
    
      return resultCount;
    };
    
    const countsByDay = async () => {
      let tweets = await getAllTweets();
    
      return tweets.reduce(
        (counts, tweet) => ({
          ...counts,
          [moment(tweet.created_at).format("ddd - MM/DD")]:
            (counts[moment(tweet.created_at).format("ddd - MM/DD")] || 0) + 1,
        }),
        {}
      );
    };
    
    const countsByLanguage = async () => {
      let counts = {};
    
      const languages = [
        "javascript",
        "JavaScript",
        "android",
        "frontend",
        "ios",
        "backend",
        "node",
        "nodejs",
        "python",
        "react",
        "scala",
        "c#",
        "rails",
        "ruby",
        "php",
        "java",
        "blockchain",
        ".net",
        "sql",
        "java",
        "php",
        "golang",
        "go",
        "wordpress",
      ];
    
      const tweets = await getAllTweets();
    
      for (tweet of tweets) {
        for (language of languages) {
          if (
            tweet.text.includes(language) ||
            tweet.text.includes(language.toUpperCase())
          ) {
            counts[language] = (counts[language] || 0) + 1;
          }
        }
      }
    
      if (counts["JavaScript"]) {
        counts["javascript"] += counts["JavaScript"];
        delete counts.JavaScript;
      }
    
      if (counts["node"]) {
        counts["nodejs"] += counts["node"];
        delete counts.node;
      }
    
      if (counts["golang"]) {
        counts["go"] += counts["golang"];
        delete counts.node;
      }
    
      return counts;
    };
    
    const sortCounts = (counts, keyName = "name") => {
      let sortedCounts = Object.keys(counts).map((language) => ({
        [keyName]: language,
        total: counts[language],
      }));
    
      sortedCounts.sort((a, b) => {
        return b.total - a.total;
      });
    
      return sortedCounts;
    };
    
    app.get("/api/search/recent/top", async (req, res) => {
      if (!BEARER_TOKEN) {
        res.status(401).send(authMessage);
      }
    
      const tweets = await getAllTweets();
      let tweetsByEngagement = {};
      for (tweet of tweets) {
        const total_engagement = Object.values(tweet.public_metrics).reduce(
          (total_engagement, public_metric) => total_engagement + public_metric
        );
        tweetsByEngagement[tweet.id] = total_engagement;
      }
    
      res.send({ result: sortCounts(tweetsByEngagement, "id")[0] });
    });
    
    app.get("/api/search/recent/count", async (req, res) => {
      if (!BEARER_TOKEN) {
        res.status(401).send(authMessage);
      }
    
      const results =
        req.query.group === "day" ? await countsByDay() : await getCount();
    
      res.send({ count: results });
    });
    
    app.get("/api/search/recent/language", async (req, res) => {
      if (!BEARER_TOKEN) {
        res.status(401).send(authMessage);
      }
    
      try {
        let results = await countsByLanguage();
        results = sortCounts(results);
        res.send({ count: results.slice(0, 10) });
      } catch (e) {
        console.log(e);
      }
    });
    
    if (process.env.NODE_ENV === "production") {
      app.use(express.static(path.join(__dirname, "../build")));
      app.get("*", (request, res) => {
        res.sendFile(path.join(__dirname, ".../build", "index.html"));
      });
    } else {
      port = 3001;
    }
    
    app.listen(port, () => console.log(`Listening on port ${port}`));
    
    서버 쪽 코드에서 다음 단점을 구축하고 있습니다
  • /api/search/recent/count 단점은 기본적으로 지난 7일의 작업 총수를 되돌려줍니다.다음 값 중 하나를 가진 그룹 조회 매개 변수를 입력하면 다음 값 중 하나를 표시합니다
  • group=day 지난 7일 동안 일별 작업 수
  • 로 돌아갑니다.
  • group=language는 지난 7일
  • 추문에서 언급한 프로그래밍 언어에 따라 세분화된 작업 수량을 되돌려줍니다 (존재한다면)
  • /api/search/recent/top 단점은 참여도가 가장 높은 추문을 되돌려줍니다.이 단점은 트윗 부하의 칭찬, 즐겨찾기, 전송, 인용을 되돌려줍니다.이 통계 데이터를 사용하면 어떤 추문이 가장 많은 관심을 받았는지 확인할 수 있다.
  • /api/search/recent 단점은 아래 검색 조회와 일치하는 추문을 되돌려줍니다
  • (developer OR software) remote (context:66.961961812492148736 OR context:66.850073441055133696) -is:retweet -"business developer”
    
    이 검색 질의는 키워드 "개발자"또는 "소프트웨어"가 포함된 트윗에서 가장 가까운 검색 엔드포인트가 트윗 텍스트의 키워드 "원격"과 일치하는 것을 나타냅니다.또한 이 검색 질의는 특정 도메인 이름과 엔티티 이름을 포함하는 트윗과 일치하는 컨텍스트 연산자를 사용합니다.
          "context_annotations": [
            {
              "domain": {
                "id": "65",
                "name": "Interests and Hobbies Vertical",
                "description": "Top level interests and hobbies groupings, like Food or Travel"
              },
              "entity": {
                "id": "847544972781826048",
                "name": "Careers",
                "description": "Careers"
              }
            },
            {
              "domain": {
                "id": "66",
                "name": "Interests and Hobbies Category",
                "description": "A grouping of interests and hobbies entities, like Novelty Food or Destinations"
              },
              "entity": {
                "id": "961961812492148736",
                "name": "Recruitment",
                "description": "Recruitment"
              }
            }
    
    컨텍스트 연산자는 다음 형식의 컨텍스트를 따릅니다.위의 예시 유효 하중에서 보듯이 역 id 65와 66은'취미와 취미 유형'을 대표한다.실체 ID 961961812492148736은'채용'실체를, 실체 ID 847544972781826048은'직업'실체를 대표한다.도메인의 전체 목록에 대해 Tweet Annotations 문서에는 가 포함되어 있습니다.
    마지막으로 운영자'-is:retweet'과'-비즈니스 개발자'는 검색 결과에서 전송을 배제하고'비즈니스 개발자'를 포함하는 모든 트윗을 배제하는 데 사용된다.리트윗은 검색 결과에서 중복되지 않도록 제외됐고,'비즈니스 개발자'가 포함된 트윗은 이와 무관하기 때문에 제외됐다.
    클라이언트 코드
    다음 단계에서는 다음 React 어셈블리를 사용하여 이러한 정보를 표시합니다.
    응용 프로그램.js - 부모 구성 요소, 모든 다른 구성 요소를 순서대로 보여 줍니다.
    트위터js- 모집 정보를 포함하는 트윗 보이기
    하루.js- 지난 7일 동안 매일 발표된 트윗 수량을 표시하는 스트라이프 맵
    꼭대기js-지난 7일 동안 참여도가 가장 높은 트윗
    트위터js-자리 표시자 구성 요소, 발표된 10가지 프로그래밍 언어, 10가지 언어를 표시합니다.js 구성 요소, 이날.js 구성 요소입니다. 여러 개의 추문을 보여 줍니다.js 구성 요소
    회전기.js - 마운트된 API 호출에 대한 로드 표시기 보이기
    이제 React 구성 요소 생성을 시작해야 합니다./src 하위 디렉터리에 "components"라는 디렉터리를 만듭니다.위의 원본 코드 파일은 이 새 디렉터리에 저장될 것입니다.우선, 응용 프로그램의 부모most 구성 요소를 만듭니다.이 구성 요소는 모든 다른 구성 요소를 보여 줍니다.
    응용 프로그램.js
    import React from "react";
    import { BrowserRouter, Route } from "react-router-dom";
    
    import Tweets from "./Tweets";
    
    const App = () => {
      return (
        <div className="ui container">
          <div className="introduction"></div>
    
          <h1 className="ui header">
            <img
              className="ui image"
              src="/Twitter_Logo_Blue.png"
              alt="Twitter Logo"
            />
            <div className="content">
              Remote Developer Job Analytics
              <div className="sub header">Powered by Twitter data</div>
            </div>
          </h1>
    
          <div className="ui grid">
            <BrowserRouter>
              <Route exact path="/" component={Tweets} />
            </BrowserRouter>
          </div>
        </div>
      );
    };
    
    export default App;
    
    다음은 직위 공고를 포함하는 트윗 예시를 보여주는 부모 구성 요소를 만듭니다.
    트위터js
    import React, { useEffect, useState } from "react";
    import axios from "axios";
    import Tweet from "./Tweet";
    import Top from "./Top";
    import Day from "./Day";
    import Spinner from "./Spinner";
    
    const initialState = {
      tweets: [],
    };
    
    const Tweets = () => {
      const [tweets, setTweets] = useState([]);
      const [tweetCount, setTweetCount] = useState(0);
      const [topTweetId, setTopTweetId] = useState(null);
      const [error, setError] = useState(null);
      const [isLoading, setIsLoading] = useState(false);
    
      useEffect(() => {
        getTweets();
        getTweetCount();
        getTopTweetId();
      }, []);
    
      const getTweets = async () => {
        try {
          setIsLoading(true);
          const response = await axios.get("/api/search/recent");
          setTweets(response.data.data);
          setIsLoading(false);
        } catch (e) {
          setError(e.response.data);
          setIsLoading(false);
        }
      };
      const getTweetCount = async () => {
        try {
          const response = await axios.get("/api/search/recent/count");
          console.log(response);
          setTweetCount(response.data.count);
        } catch (e) {
          setError(e.response.data);
          setIsLoading(false);
        }
      };
    
      const getTopTweetId = async () => {
        const response = await axios.get("/api/search/recent/top");
        setTopTweetId(response.data.result.id);
      };
    
      const errors = () => {
        if (error) {
          return (
            <div className="sixteen wide column">
              <div className="ui message negative">
                <div className="header">{error.title}</div>
                <p key={error.detail}>{error.detail}</p>
                <em>
                  See
                  <a href={error.type} target="_blank" rel="noopener noreferrer">
                    {" "}
                    Twitter documentation{" "}
                  </a>
                  for further details.
                </em>
              </div>
            </div>
          );
        }
      };
    
      const dashboard = () => {
        if (!isLoading) {
          if (!error) {
            return (
              <React.Fragment>
                <div className="sixteen wide column">
                  <div className="ui segment">
                    <div className="ui header center aligned ">
                      Total number of Tweets
                    </div>
                    <div className="ui header center aligned ">{tweetCount}</div>
                  </div>
                </div>
                <div className="eight wide column">
                  <div className="ui segment">
                    <Top />
                  </div>
                </div>
                <div className="eight wide column">
                  <div className="ui segment">
                    <Day />
                  </div>
                </div>
                <div className="eight wide column">
                  <div className="ui header">Top Tweet</div>
                  <Tweet key={topTweetId} id={topTweetId} />
                </div>
                <div className="eight wide column">
                  <div className="ui basic segment">
                    <div className="ui header">Recent Tweets</div>
                    {tweets.map((tweet) => (
                      <Tweet key={tweet.id} id={tweet.id} />
                    ))}
                  </div>
                </div>
              </React.Fragment>
            );
          }
        } else {
          return <Spinner />;
        }
      };
    
      return (
        <React.Fragment>
          {errors()}
          {dashboard()}
        </React.Fragment>
      );
    };
    
    export default Tweets;
    
    다음으로 참여도가 가장 높은 트윗을 보여주는 구성 요소를 만듭니다.
    꼭대기js
    import React, { useEffect, useState } from "react";
    import axios from "axios";
    import Spinner from "./Spinner";
    
    const Top = () => {
      const [countByLanguage, setCountByLanguage] = useState([]);
      const [isLoading, setIsLoading] = useState(false);
    
      const style = {
        fontSize: "17px",
      };
    
      useEffect(() => {
        getTopLanguages();
      }, []);
    
      const getTopLanguages = async () => {
        setIsLoading(true);
        const response = await axios.get("/api/search/recent/language");
        setCountByLanguage(response.data.count);
        setIsLoading(false);
      };
    
      const capitalize = (word) => {
        const first_letter = word.slice(0, 1).toUpperCase();
        return first_letter + word.slice(1);
      };
    
      const displayTopLanuguages = () => {
        {
          if (!isLoading) {
            return countByLanguage.map((count, i) => (
              <div style={style} className="item">
                {i + 1}. {capitalize(count.name)}
              </div>
            ));
          } else {
            return <Spinner />;
          }
        }
      };
    
      return (
        <React.Fragment>
          <div className="ui header">Top Programming Languages</div>
          <ul className="ui relaxed list"> {displayTopLanuguages()}</ul>
        </React.Fragment>
      );
    };
    
    export default Top;
    
    다음에 하나의 트윗을 보여주는 구성 요소를 만듭니다.
    트위터js
    import React from "react";
    import { TwitterTweetEmbed } from "react-twitter-embed";
    
    const Tweet = ({ id }) => {
      const options = {
        cards: "hidden",
        align: "left",
        width: "550",
        conversation: "none",
      };
    
      return <TwitterTweetEmbed options={options} tweetId={id} />;
    };
    
    export default Tweet;
    
    마지막으로 마운트된 API 호출 시 로드 표시기를 표시할 구성 요소를 생성합니다.
    import React from "react";
    
    const Spinner = () => {
      return (
        <div>
          <div className="ui active centered large inline loader">
            <img
              className="ui image"
              src="/Twitter_Logo_Blue.png"
              alt="Twitter Logo"
            />
          </div>
        </div>
      );
    };
    
    export default Spinner;
    
    프록시 설정
    마지막 단계는 클라이언트의 요청 에이전트를 백엔드 서버에 연결하는 것입니다.이를 위해 src/디렉터리에 'setupProxy.js' 라는 새 파일을 만들고 다음 코드를 추가합니다.
    에이전트를 설정합니다.js
    const { createProxyMiddleware } = require("http-proxy-middleware");
    
    // This proxy redirects requests to /api endpoints to
    // the Express server running on port 3001.
    module.exports = function (app) {
      app.use(
        ["/api"],
        createProxyMiddleware({
          target: "http://localhost:3001",
        })
      );
    };
    
    
    이제 프로젝트 루트 디렉토리로 이동하여 다음 명령을 입력하여 서버와 클라이언트를 시작할 수 있습니다.
    npm start
    
    이 명령이 완료되면 기본 웹 브라우저가 자동으로 시작되고 http://localhost:3000 로 이동합니다. 지난 7일간의 채용 공고와 소개에 제출한 모든 질문에 대한 답변 예시를 볼 수 있습니다.
    결론
    최근 검색 노드를 사용하여 지난 7일 동안 원격 개발자 직위 공고에 대한 질문에 대답할 프로그램을 만들었습니다.이러한 질문에 대한 답은 포부를 가진 개발자나 이미 일자리가 있고 다음 일자리를 어떻게 찾는지에 대해 더욱 전략적인 개발자에게 매우 유용하다.
    너는 이 앱을 확장할 재미있는 방법을 찾았니?트위터로 알려줘.이 자습서는 Twitter API 이외의 몇 개의 라이브러리를 사용해서 만들었지만, 서로 다른 수요와 요구가 있을 수 있습니다. 이 도구들이 당신에게 적합한지 평가해야 합니다.

    좋은 웹페이지 즐겨찾기