├── .gitignore ├── requirements.txt ├── accounts.json.sample ├── .github ├── dependabot.yml └── workflows │ └── action.yml ├── run.py ├── setup.py ├── LICENSE ├── naver ├── find.py └── session.py ├── naver_paper_ppomppu.py ├── naver_paper_ruliweb.py ├── naver_paper_clien.py ├── naver_paper_damoang.py ├── README.md ├── main.py └── run_new.py /.gitignore: -------------------------------------------------------------------------------- 1 | log/ 2 | __pycache__/ 3 | run.sh 4 | *.log 5 | user_dir 6 | *.txt 7 | *.json -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | bs4==0.0.1 2 | beautifulsoup4==4.12.2 3 | certifi==2024.7.4 4 | charset-normalizer==3.3.2 5 | idna==3.7 6 | selenium==4.16.0 7 | requests==2.32.0 8 | soupsieve==2.5 9 | urllib3==2.2.2 10 | webdriver-manager==4.0.2 11 | -------------------------------------------------------------------------------- /accounts.json.sample: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "ID 1", 4 | "pw": "Password 1", 5 | "ua": "UA 1", 6 | "mobile_device": "iPhone SE" 7 | }, 8 | { 9 | "id": "ID 2", 10 | "pw": null, 11 | "ua": null, 12 | "mobile_device": "iPhone 12 Pro" 13 | } 14 | ] 15 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | 13 | -------------------------------------------------------------------------------- /run.py: -------------------------------------------------------------------------------- 1 | import time 2 | from naver import session as s 3 | from naver import find as f 4 | 5 | base_url = "https://www.clien.net/service/board/jirum" 6 | 7 | if __name__ == "__main__": 8 | s = s.session("##ID##", "##PASSWORD##") 9 | campaign_links = f.find(base_url) 10 | if campaign_links == []: 11 | print("모든 링크를 방문했습니다.") 12 | for link in campaign_links: 13 | response = s.get(link) 14 | print(response.text) # for debugging 15 | response.raise_for_status() 16 | time.sleep(5) 17 | print("캠페인 URL : " + link) 18 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | setup( 4 | name='naverpaper', 5 | version='0.1', 6 | author='stateofai', 7 | author_email='tellme@duck.com', 8 | description='Naver Paper Machine', 9 | packages=find_packages(), 10 | install_requires=[ 11 | 'requests', 12 | 'BeautifulSoup4', 13 | 'lxml', 14 | 'pyc', 15 | 'rsa', 16 | 'urllib3', 17 | ], 18 | classifiers=[ 19 | 'Programming Language :: Python :: 3', 20 | 'License :: OSI Approved :: MIT License', 21 | 'Operating System :: OS Independent', 22 | ], 23 | python_requires='>=3.9', 24 | ) 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 stateofai 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/action.yml: -------------------------------------------------------------------------------- 1 | name: Naver Paper Python GitHub Actions 2 | on: 3 | workflow_dispatch: 4 | # schedule: 5 | # - cron: '*/30 * * * *' 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v3 11 | - name: Set up Python 3.11 12 | uses: actions/setup-python@v3 13 | with: 14 | python-version: "3.11" 15 | cache: 'pip' # caching pip dependencies 16 | - name: Install dependencies 17 | run: | 18 | wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb 19 | sudo apt-get update 20 | sudo apt-get install -y gdebi-core 21 | sudo gdebi google-chrome-stable_current_amd64.deb 22 | python -m pip install --upgrade pip 23 | pip install -r requirements.txt 24 | - name: Run 25 | run: | 26 | mkdir -p ~/.naver 27 | echo "[default]" > ~/.naver/credentials 28 | echo "username = " $USERNAME >> ~/.naver/credentials 29 | echo "password = " $PASSWORD >> ~/.naver/credentials 30 | python run_new.py 31 | env: 32 | USERNAME: ${{ secrets.USERNAME }} # secret에 저장된 내용을 불러옴 33 | PASSWORD: ${{ secrets.PASSWORD }} 34 | CREDENTIALENV : ${{ secrets.CREDENTIALENV }} 35 | -------------------------------------------------------------------------------- /naver/find.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from bs4 import BeautifulSoup 3 | from urllib.parse import urljoin 4 | 5 | def find(base_url, visited_urls_file="visited_urls.txt"): 6 | # Read visited URLs from file 7 | try: 8 | with open(visited_urls_file, "r") as file: 9 | visited_urls = set(file.read().splitlines()) 10 | except FileNotFoundError: 11 | visited_urls = set() 12 | 13 | # Send a request to the base URL 14 | response = requests.get(base_url) 15 | soup = BeautifulSoup(response.text, "html.parser") 16 | 17 | # Find all span elements with class 'list_subject' and get 'a' tags 18 | list_subject_links = soup.find_all("span", class_="list_subject") 19 | naver_links = [] 20 | for span in list_subject_links: 21 | a_tag = span.find("a", href=True) 22 | if a_tag and "네이버" in a_tag.text: 23 | naver_links.append(a_tag["href"]) 24 | 25 | # Initialize a list to store campaign links 26 | campaign_links = [] 27 | 28 | # Check each Naver link 29 | for link in naver_links: 30 | full_link = urljoin(base_url, link) 31 | if full_link in visited_urls: 32 | continue # Skip already visited links 33 | 34 | res = requests.get(full_link) 35 | inner_soup = BeautifulSoup(res.text, "html.parser") 36 | 37 | # Find all links that start with the campaign URL 38 | for a_tag in inner_soup.find_all("a", href=True): 39 | if a_tag["href"].startswith("https://campaign2-api.naver.com"): 40 | campaign_links.append(a_tag["href"]) 41 | 42 | # Add the visited link to the set 43 | visited_urls.add(full_link) 44 | 45 | # Save the updated visited URLs to the file 46 | with open(visited_urls_file, "w") as file: 47 | for url in visited_urls: 48 | file.write(url + "\n") 49 | 50 | return campaign_links 51 | -------------------------------------------------------------------------------- /naver_paper_ppomppu.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from urllib.parse import urljoin 3 | from bs4 import BeautifulSoup 4 | 5 | base_url = "https://www.ppomppu.co.kr/zboard/zboard.php?id=coupon" 6 | page_url = "https://www.ppomppu.co.kr/zboard/zboard.php?" 7 | site = "ppmppu" 8 | 9 | def find_naver_campaign_links(visited_urls_file='visited_urls_'+ site +'.txt'): 10 | try: 11 | with open(visited_urls_file, 'r') as file: 12 | visited_urls = set(file.read().splitlines()) 13 | except FileNotFoundError: 14 | visited_urls = set() 15 | 16 | try: 17 | response = requests.get(base_url, timeout=5) 18 | print(f"{site}\tlist get HTTP STATUS : {response.status_code}") 19 | except: 20 | print(f"{site}\tlist get error\r\n{base_url}") 21 | return [] 22 | soup = BeautifulSoup(response.text, 'html.parser') 23 | 24 | list_subject_links = soup.find_all('td', class_='baseList-space') 25 | 26 | naver_links = [] 27 | for span in list_subject_links: 28 | a_tag = span.find('a', href=True) 29 | 30 | if a_tag and '네이버' in a_tag.text: 31 | naver_links.append(a_tag['href']) 32 | 33 | # Initialize a list to store campaign links 34 | campaign_links = [] 35 | 36 | # Check each naver_links 37 | for link in naver_links: 38 | full_link = urljoin(page_url, link) 39 | print(f"{site}\tlinks : " + full_link) 40 | if full_link in visited_urls: 41 | continue # Skip already visited links 42 | 43 | try: 44 | res = requests.get(full_link) 45 | except: 46 | print(f"{site}\tfull link get error\r\n{full_link}") 47 | pass 48 | inner_soup = BeautifulSoup(res.text, 'html.parser') 49 | 50 | campaign_a_tags = inner_soup.find_all('a', href=True) 51 | 52 | for a_tag in campaign_a_tags: 53 | campaign_link = a_tag.get_text().strip() 54 | 55 | if ('campaign2-api.naver.com' in campaign_link or 'ofw.adison.co' in campaign_link) and campaign_link not in campaign_links: 56 | campaign_links.append(campaign_link) 57 | 58 | # Add the visited link to the set 59 | visited_urls.add(full_link) 60 | 61 | # Save the updated visited URLs to the file 62 | with open(visited_urls_file, 'w') as file: 63 | for url in visited_urls: 64 | file.write(url + '\n') 65 | 66 | return campaign_links 67 | 68 | 69 | if __name__ == "__main__": 70 | find_naver_campaign_links() -------------------------------------------------------------------------------- /naver_paper_ruliweb.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from urllib.parse import urljoin 3 | from bs4 import BeautifulSoup 4 | 5 | base_url = "https://bbs.ruliweb.com/market/board/1020" 6 | site = 'ruliweb' 7 | 8 | def find_naver_campaign_links(visited_urls_file='visited_urls_'+ site +'.txt'): 9 | # Read visited URLs from file 10 | try: 11 | with open(visited_urls_file, 'r') as file: 12 | visited_urls = set(file.read().splitlines()) 13 | except FileNotFoundError: 14 | visited_urls = set() 15 | 16 | # Send a request to the base URL 17 | try: 18 | response = requests.get(base_url, timeout=5) 19 | print(f"{site}\tlist get HTTP STATUS : {response.status_code}") 20 | except: 21 | print(f"{site}\tlist get error\r\n{base_url}") 22 | return [] 23 | soup = BeautifulSoup(response.text, 'html.parser') 24 | 25 | # Find all span elements with class 'list_subject' and get 'a' tags 26 | list_subject_links = soup.find_all('td', class_='subject') 27 | 28 | naver_links = [] 29 | for span in list_subject_links: 30 | a_tag = span.find('a', href=True) 31 | if a_tag and '네이버' in a_tag.text: 32 | naver_links.append(a_tag['href']) 33 | 34 | # Initialize a list to store campaign links 35 | campaign_links = [] 36 | 37 | # Check each Naver link 38 | for link in naver_links: 39 | full_link = link; 40 | print(f"{site}\tlinks : " + full_link) 41 | if full_link in visited_urls: 42 | continue # Skip already visited links 43 | 44 | try: 45 | res = requests.get(full_link) 46 | except: 47 | print(f"{site}\tfull link get error\r\n{full_link}") 48 | pass 49 | inner_soup = BeautifulSoup(res.text, 'html.parser') 50 | 51 | # Find all links that start with the campaign URL 52 | for a_tag in inner_soup.find_all('a', href=True): 53 | campaign_link = a_tag['href'] 54 | 55 | if ('campaign2-api.naver.com' in campaign_link or 'ofw.adison.co' in campaign_link) and campaign_link not in campaign_links: 56 | campaign_links.append(campaign_link) 57 | 58 | # Add the visited link to the set 59 | visited_urls.add(full_link) 60 | 61 | # Save the updated visited URLs to the file 62 | with open(visited_urls_file, 'w') as file: 63 | for url in visited_urls: 64 | file.write(url + '\n') 65 | 66 | return campaign_links 67 | 68 | 69 | if __name__ == "__main__": 70 | find_naver_campaign_links() -------------------------------------------------------------------------------- /naver_paper_clien.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from urllib.parse import urljoin 3 | from bs4 import BeautifulSoup 4 | 5 | base_url = "https://www.clien.net/service/board/jirum" 6 | site = "clien" 7 | 8 | def find_naver_campaign_links(visited_urls_file='visited_urls_'+ site +'.txt'): 9 | # Read visited URLs from file 10 | try: 11 | with open(visited_urls_file, 'r') as file: 12 | visited_urls = set(file.read().splitlines()) 13 | except FileNotFoundError: 14 | visited_urls = set() 15 | 16 | # Send a request to the base URL 17 | try: 18 | response = requests.get(base_url, timeout=5) 19 | print(f"{site}\tlist get HTTP STATUS : {response.status_code}") 20 | except: 21 | print(f"{site}\tlist get error\r\n{base_url}") 22 | pass 23 | soup = BeautifulSoup(response.text, 'html.parser') 24 | 25 | # Find all span elements with class 'list_subject' and get 'a' tags 26 | list_subject_links = soup.find_all('span', class_='list_subject') 27 | 28 | naver_links = [] 29 | for span in list_subject_links: 30 | a_tag = span.find('a', href=True) 31 | if a_tag and '네이버' in a_tag.text: 32 | naver_links.append(a_tag['href']) 33 | 34 | # Initialize a list to store campaign links 35 | campaign_links = [] 36 | 37 | # Check each Naver link 38 | for link in naver_links: 39 | full_link = urljoin(base_url, link) 40 | print(f"{site}\tlinks : " + full_link) 41 | if full_link in visited_urls: 42 | continue # Skip already visited links 43 | 44 | try: 45 | res = requests.get(full_link) 46 | except: 47 | print(f"{site}\tfull link get error\r\n{full_link}") 48 | pass 49 | inner_soup = BeautifulSoup(res.text, 'html.parser') 50 | 51 | # Find all links that start with the campaign URL 52 | for a_tag in inner_soup.find_all('a', href=True): 53 | campaign_link = a_tag['href'] 54 | 55 | if ('campaign2-api.naver.com' in campaign_link or 'ofw.adison.co' in campaign_link) and campaign_link not in campaign_links: 56 | campaign_links.append(campaign_link) 57 | 58 | # Add the visited link to the set 59 | visited_urls.add(full_link) 60 | 61 | # Save the updated visited URLs to the file 62 | with open(visited_urls_file, 'w') as file: 63 | for url in visited_urls: 64 | file.write(url + '\n') 65 | 66 | return campaign_links 67 | 68 | 69 | if __name__ == "__main__": 70 | find_naver_campaign_links() -------------------------------------------------------------------------------- /naver_paper_damoang.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from urllib.parse import urljoin 3 | from bs4 import BeautifulSoup 4 | 5 | # disable InsecureRequestWarning 6 | requests.packages.urllib3.disable_warnings(requests.packages.urllib3.exceptions.InsecureRequestWarning) 7 | 8 | base_url = "https://www.damoang.net/economy" 9 | site = "damoang" 10 | 11 | def find_naver_campaign_links(visited_urls_file='visited_urls_'+ site +'.txt'): 12 | # Read visited URLs from file 13 | try: 14 | with open(visited_urls_file, 'r') as file: 15 | visited_urls = set(file.read().splitlines()) 16 | except FileNotFoundError: 17 | visited_urls = set() 18 | 19 | # Send a request to the base URL 20 | request_headers = { 21 | 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36' 22 | } 23 | try: 24 | response = requests.get(base_url, timeout=5, headers=request_headers, verify=False) 25 | print(f"{site}\tlist get HTTP STATUS : {response.status_code}") 26 | except: 27 | print(f"{site}\tlist get error\r\n{base_url}") 28 | return [] 29 | soup = BeautifulSoup(response.text, 'html.parser') 30 | 31 | # Find all span elements with class 'list_subject' and get 'a' tags 32 | list_subject_links = soup.find_all('li', class_='list-group-item') 33 | 34 | naver_links = [] 35 | for span in list_subject_links: 36 | a_tag = span.find('a', href=True) 37 | if a_tag and '네이버' in a_tag.text: 38 | naver_links.append(a_tag['href']) 39 | 40 | # Initialize a list to store campaign links 41 | campaign_links = [] 42 | 43 | # Check each Naver link 44 | for link in naver_links: 45 | full_link = urljoin(base_url, link) 46 | print(f"{site}\tlinks : " + full_link) 47 | if full_link in visited_urls: 48 | continue # Skip already visited links 49 | 50 | try: 51 | res = requests.get(full_link, headers=request_headers) 52 | except: 53 | print(f"{site}\tfull link get error\r\n{full_link}") 54 | pass 55 | inner_soup = BeautifulSoup(res.text, 'html.parser') 56 | 57 | # Find all links that start with the campaign URL 58 | for a_tag in inner_soup.find_all('a', href=True): 59 | campaign_link = a_tag['href'] 60 | 61 | if ('campaign2-api.naver.com' in campaign_link or 'ofw.adison.co' in campaign_link) and campaign_link not in campaign_links: 62 | campaign_links.append(campaign_link) 63 | 64 | # Add the visited link to the set 65 | visited_urls.add(full_link) 66 | 67 | # Save the updated visited URLs to the file 68 | with open(visited_urls_file, 'w') as file: 69 | for url in visited_urls: 70 | file.write(url + '\n') 71 | 72 | return campaign_links 73 | 74 | 75 | if __name__ == "__main__": 76 | find_naver_campaign_links() 77 | -------------------------------------------------------------------------------- /naver/session.py: -------------------------------------------------------------------------------- 1 | import time 2 | import requests 3 | import uuid 4 | import re 5 | import rsa 6 | import lzstring 7 | from requests.adapters import HTTPAdapter 8 | from urllib3.util.retry import Retry 9 | from bs4 import BeautifulSoup 10 | from urllib.parse import urljoin 11 | 12 | 13 | def naver_style_join(elements): 14 | """Join elements in Naver's specific format.""" 15 | return "".join([chr(len(s)) + s for s in elements]) 16 | 17 | 18 | def encrypt(key_str, user_id, user_password): 19 | """Encrypt user credentials using RSA encryption.""" 20 | session_key, key_name, e_str, n_str = key_str.split(",") 21 | e, n = int(e_str, 16), int(n_str, 16) 22 | 23 | message = naver_style_join([session_key, user_id, user_password]).encode() 24 | pubkey = rsa.PublicKey(e, n) 25 | encrypted = rsa.encrypt(message, pubkey) 26 | 27 | return key_name, encrypted.hex() 28 | 29 | 30 | def get_encryption_key(): 31 | """Retrieve the encryption key from Naver.""" 32 | try: 33 | response = requests.get("https://nid.naver.com/login/ext/keys.nhn") 34 | response.raise_for_status() 35 | return response.content.decode("utf-8") 36 | except requests.RequestException as e: 37 | raise ConnectionError("Failed to retrieve encryption key.") from e 38 | 39 | 40 | def encrypt_account(user_id, user_password): 41 | """Encrypt user account credentials.""" 42 | key_str = get_encryption_key() 43 | return encrypt(key_str, user_id, user_password) 44 | 45 | 46 | def session(user_id, user_password): 47 | """Create and return a Naver session.""" 48 | try: 49 | encrypted_name, encrypted_password = encrypt_account(user_id, user_password) 50 | session = requests.Session() 51 | retries = Retry( 52 | total=5, backoff_factor=0.1, status_forcelist=[500, 502, 503, 504] 53 | ) 54 | session.mount("https://", HTTPAdapter(max_retries=retries)) 55 | 56 | request_headers = {"User-agent": "Mozilla/5.0"} 57 | bvsd_uuid = uuid.uuid4() 58 | encData = ( 59 | '{"a":"%s-4","b":"1.3.4","d":[{"i":"id","b":{"a":["0,%s"]},"d":"%s","e":false,"f":false},{"i":"%s","e":true,"f":false}],"h":"1f","i":{"a":"Mozilla/5.0"}}' 60 | % (bvsd_uuid, user_id, user_id, user_password) 61 | ) 62 | bvsd = '{"uuid":"%s","encData":"%s"}' % ( 63 | bvsd_uuid, 64 | lzstring.LZString.compressToEncodedURIComponent(encData), 65 | ) 66 | 67 | response = session.post( 68 | "https://nid.naver.com/nidlogin.login", 69 | data={ 70 | "svctype": "0", 71 | "enctp": "1", 72 | "encnm": encrypted_name, 73 | "enc_url": "http0X0.0000000000001P-10220.0000000.000000www.naver.com", 74 | "url": "www.naver.com", 75 | "smart_level": "1", 76 | "encpw": encrypted_password, 77 | "bvsd": bvsd, 78 | }, 79 | headers=request_headers, 80 | ) 81 | 82 | finalize_url = re.search( 83 | r'location\.replace\("([^"]+)"\)', response.content.decode("utf-8") 84 | ).group(1) 85 | session.get(finalize_url) 86 | return session 87 | 88 | except Exception as e: 89 | raise ConnectionError("Failed to create Naver session.") from e 90 | 91 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Naver Paper Python GitHub Actions](https://github.com/stateofai/naver-paper/actions/workflows/action.yml/badge.svg)](https://github.com/stateofai/naver-paper/actions/workflows/action.yml) 2 | 3 | > 기존 requests 모듈을 이용한 로그인이 작동하지 않아 selenium을 사용하도록 변경되었습니다. (Thanks to @bagng) 4 | > chromedriver 설치 후 코드를 실행해주세요. 5 | > 리눅스(Ubuntu 22.04) 및 맥(macOS Sonoma)에서 작동 되는 것을 확인했습니다. 6 | > 윈도우는 확인해보지 못했으나, 혹시 실행되신 분이 있으면 알려주세요. 7 | 8 | ### GitHub Actions 사용 9 | 1. 이 repo를 fork 10 | 2. secrets에 ID, PASSWORD 항목에 네이버 ID 및 패스워드 입력. ID라는 이름으로 네이버 ID를 넣고 PASSWORD라는 항목에 패스워드 입력 11 | CREDENTIALENV 항목에 복수 계정 입력. 값은 [{"id":"ID_1","pw":"PW_1"},{"id":"ID_2","pw":"PW_2"}] 형태 이어야 합니다. 12 | (Settings -> Secrets and variable -> Actions -> New repository secret) 13 | 3. 30분마다 주기적으로 실행되는 것을 확인 14 | 4. secrets TRY_LOGIN 항목에 로그인 재시도 횟수 입력, 기본값은 3번 15 | 16 | ## Prerequisites 17 | ### Install Google Chrome 18 | ```bash 19 | $ wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb 20 | $ sudo apt-get update 21 | $ sudo apt-get install -y gdebi-core 22 | $ sudo gdebi google-chrome-stable_current_amd64.deb 23 | ``` 24 | > Verifying Google Chrome Installation 25 | ```bash 26 | $ google-chrome --version 27 | Google Chrome 120.0.6099.224 28 | ``` 29 | > Install ChromeDriver 30 | - Go to [https://googlechromelabs.github.io/chrome-for-testing/] 31 | - Download ChromeDriver same as Google Chrome Version 32 | - Unzip and Copy chromedriver binary file to /usr/bin/chromedriver 33 | > This is example 34 | ```bash 35 | $ google-chrome --version 36 | Google Chrome 120.0.6099.109 37 | $ wget https://edgedl.me.gvt1.com/edgedl/chrome/chrome-for-testing/120.0.6099.109/linux64/chromedriver-linux64.zip 38 | --2024-01-17 10:01:07-- https://edgedl.me.gvt1.com/edgedl/chrome/chrome-for-testing/120.0.6099.109/linux64/chromedriver-linux64.zip 39 | Resolving edgedl.me.gvt1.com (edgedl.me.gvt1.com)... 34.104.35.123, 2600:1900:4110:86f:: 40 | Connecting to edgedl.me.gvt1.com (edgedl.me.gvt1.com)|34.104.35.123|:443... connected. 41 | HTTP request sent, awaiting response... 200 OK 42 | Length: 8624482 (8.2M) [application/octet-stream] 43 | Saving to: ‘chromedriver-linux64.zip’ 44 | chromedriver-linux64.zip 100%[=======================================>] 8.22M 5.21MB/s in 1.6s 45 | 2024-01-17 10:01:10 (5.21 MB/s) - ‘chromedriver-linux64.zip’ saved [8624482/8624482] 46 | $ unzip chromedriver-linux64.zip 47 | Archive: chromedriver-linux64-120.0.6099.109.zip 48 | inflating: chromedriver-linux64/LICENSE.chromedriver 49 | inflating: chromedriver-linux64/chromedriver 50 | $ sudo cp chromedriver-linux64/chromedriver /usr/local/bin 51 | $ sudo chmod a+x /usr/local/bin/chromedriver 52 | $ /usr/local/bin/chromedriver --version 53 | ChromeDriver 120.0.6099.109 (3419140ab665596f21b385ce136419fde0924272-refs/branch-heads/6099@{#1483}) 54 | ``` 55 | ## Usage 56 | ``` 57 | $ git clone https://github.com/stateofai/naver-paper.git 58 | $ cd naver-paper 59 | $ pip install -r requirements.txt 60 | 61 | # 환경 변수로 USERNAME, PASSWORD 단일 계정 읽어서 실행 62 | $ python run_new.py 63 | 64 | # 환경 변수로 CREDENTIALENV 복수 계정 읽어서 실행 65 | # CREDENTIALENV 값은 [{"id":"ID_1","pw":"PW_1"},{"id":"ID_2","pw":"PW_2"}] 형태 이어야 합니다. 66 | $ python run_new.py 67 | 68 | # argument 로 id, pw 입력 69 | $ python run_new.py -i YOUR_ID -p YOUR_PW 70 | 71 | # argument 로 멀티 계정 입력 72 | $ python run_new.py -c '[{"id":"ID_1","pw":"PW_1"},{"id":"ID_2","pw":"PW_2"}]' 73 | 74 | # 브라우저 표시 --no-headless 75 | $ python run_new.py -c '[{"id":"ID_1","pw":"PW_1"}]' --no-headless 76 | 77 | # credential-file로 로그인 78 | $ python run_new.py -cf accounts.json 79 | ``` 80 | 81 | ## Contribution 82 | * 저는 전문개발자가 아니라 코드의 품질은 낮을 수 있습니다. 많은 능력자분들이 기여를 해주시면 좋겠어요 83 | 84 | ## References 85 | * https://edgedl.me.gvt1.com/edgedl/chrome/chrome-for-testing/120.0.6099.109/win64/chromedriver-win64.zip 86 | * https://help.naver.com/service/5640/contents/10219?lang=ko 87 | * https://help.naver.com/service/5640/contents/8584?lang=ko 88 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | from selenium import webdriver 2 | from selenium.webdriver.common.by import By 3 | import time 4 | import requests 5 | from requests.packages.urllib3.exceptions import InsecureRequestWarning 6 | from urllib.parse import urljoin 7 | from bs4 import BeautifulSoup 8 | import argparse 9 | 10 | def find_naver_campaign_links(source_urls, visited_urls_file='visited_urls.txt'): 11 | # Read Source URLs from file 12 | if source_urls == []: 13 | return 14 | 15 | # Read visited URLs from file 16 | try: 17 | with open(visited_urls_file, 'r') as file: 18 | visited_urls = set(file.read().splitlines()) 19 | except FileNotFoundError: 20 | visited_urls = set() 21 | 22 | requests.packages.urllib3.disable_warnings(InsecureRequestWarning) 23 | naver_links = [] 24 | for base_url in source_urls: 25 | # Send a request to the base URL 26 | response = requests.get(base_url, verify=False) 27 | soup = BeautifulSoup(response.text, 'html.parser') 28 | 29 | if base_url.startswith("https://www.clien.net"): 30 | # Find all span elements with class 'list_subject' and get 'a' tags 31 | list_subject_links = soup.find_all('span', class_='list_subject') 32 | for span in list_subject_links: 33 | a_tag = span.find('a', href=True) 34 | if a_tag and '네이버' in a_tag.text: 35 | if a_tag['href'].startswith(base_url): 36 | full_link = a_tag['href'] 37 | else: 38 | full_link = urljoin(base_url, a_tag['href']) 39 | naver_links.append(full_link) 40 | elif base_url.startswith("https://damoang.net"): 41 | # Find all elements 42 | list_subject_links = soup.find_all('a') 43 | for a_tag in list_subject_links: 44 | if a_tag and '네이버' in a_tag.text and a_tag['href'].startswith(base_url): 45 | if a_tag['href'].startswith(base_url): 46 | full_link = a_tag['href'] 47 | else: 48 | full_link = urljoin(base_url, a_tag['href']) 49 | naver_links.append(full_link) 50 | 51 | print(naver_links) 52 | 53 | # Initialize a list to store campaign links 54 | campaign_links = [] 55 | 56 | # Check each Naver link 57 | for link in naver_links: 58 | if link in visited_urls: 59 | continue # Skip already visited links 60 | 61 | res = requests.get(link, verify=False) 62 | inner_soup = BeautifulSoup(res.text, 'html.parser') 63 | 64 | # Find all links that start with the campaign URL 65 | for a_tag in inner_soup.find_all('a', href=True): 66 | if a_tag['href'].startswith("https://campaign2-api.naver.com") or a_tag['href'].startswith("https://ofw.adison.co"): 67 | campaign_links.append(a_tag['href']) 68 | 69 | # Add the visited link to the set 70 | visited_urls.add(link) 71 | 72 | # Save the updated visited URLs to the file 73 | with open(visited_urls_file, 'w') as file: 74 | for url in visited_urls: 75 | file.write(url + '\n') 76 | 77 | return campaign_links 78 | 79 | 80 | def run(id, passwd, debug) -> None: 81 | # The base URL to start with 82 | source_urls = [] 83 | source_urls.append("https://www.clien.net/service/board/jirum") 84 | source_urls.append("https://damoang.net/economy") 85 | campaign_links = find_naver_campaign_links(source_urls, "/data/visited_urls.txt") 86 | if campaign_links == []: 87 | print("모든 링크를 방문했습니다.") 88 | return 89 | if debug: 90 | print("campaign_links.count: ", len(campaign_links)) 91 | 92 | # 크롬 드라이버 옵션 설정 93 | chrome_options = webdriver.ChromeOptions() 94 | chrome_options.add_argument('--headless') 95 | chrome_options.add_argument('--no-sandbox') 96 | chrome_options.add_argument('--disable-dev-shm-usage') 97 | chrome_options.add_argument("--disable-renderer-backgrounding"); 98 | chrome_options.add_argument("--disable-background-timer-throttling"); 99 | chrome_options.add_argument("--disable-backgrounding-occluded-windows"); 100 | chrome_options.add_argument("--disable-client-side-phishing-detection"); 101 | chrome_options.add_argument("--disable-crash-reporter"); 102 | chrome_options.add_argument("--disable-oopr-debug-crash-dump"); 103 | chrome_options.add_argument("--no-crash-upload"); 104 | chrome_options.add_argument("--disable-gpu"); 105 | chrome_options.add_argument("--disable-extensions"); 106 | chrome_options.add_argument("--disable-plugins"); 107 | chrome_options.add_argument("--disable-low-res-tiling"); 108 | 109 | # 새로운 창 생성 110 | if debug: 111 | print("크롬 실행 중...") 112 | driver = webdriver.Chrome(options=chrome_options) 113 | 114 | if debug: 115 | print("네이버 접속 중...") 116 | driver.get('https://naver.com') 117 | 118 | # 현재 열려 있는 창 가져오기 119 | current_window_handle = driver.current_window_handle 120 | 121 | # 일때 해당 링크 클릭 122 | driver.find_element(By.XPATH, "//a[@class='MyView-module__link_login___HpHMW']").click() 123 | 124 | # 새롭게 생성된 탭의 핸들을 찾습니다 125 | # 만일 새로운 탭이 없을경우 기존 탭을 사용합니다. 126 | new_window_handle = None 127 | for handle in driver.window_handles: 128 | if handle != current_window_handle: 129 | new_window_handle = handle 130 | break 131 | else: 132 | new_window_handle = handle 133 | 134 | # 새로운 탭을 driver2로 지정합니다 135 | driver.switch_to.window(new_window_handle) 136 | driver2 = driver 137 | 138 | if debug: 139 | print("네이버 로그인 입력 컨트롤 찾는 중...") 140 | 141 | username = driver2.find_element(By.NAME, 'id') 142 | pw = driver2.find_element(By.NAME, 'pw') 143 | 144 | # ID input 클릭 145 | username.click() 146 | # js를 사용해서 붙여넣기 발동 <- 왜 일부러 이러냐면 pypyautogui랑 pyperclip를 사용해서 복붙 기능을 했는데 운영체제때문에 안되서 이렇게 한거다. 147 | driver2.execute_script("arguments[0].value = arguments[1]", username, id) 148 | time.sleep(1) 149 | 150 | pw.click() 151 | driver2.execute_script("arguments[0].value = arguments[1]", pw, passwd) 152 | time.sleep(1) 153 | 154 | #입력을 완료하면 로그인 버튼 클릭 155 | driver2.find_element(By.CLASS_NAME, "btn_login").click() 156 | time.sleep(1) 157 | 158 | if debug: 159 | print("네이버 로그인 완료") 160 | 161 | for link in campaign_links: 162 | print(link) # for debugging 163 | # Send a request to the base URL 164 | driver2.get(link) 165 | if link.startswith("https://campaign2-api.naver.com"): 166 | try: 167 | result = driver2.switch_to.alert 168 | print(result.text) 169 | result.accept() 170 | except: 171 | print("no alert") 172 | pageSource = driver2.page_source 173 | print(pageSource) 174 | time.sleep(5) 175 | 176 | 177 | parser = argparse.ArgumentParser() 178 | parser.add_argument('-i', '--id', type=str, required=True, help="naver id") 179 | parser.add_argument('-p', '--pwd', type=str, required=True, help="naver password") 180 | parser.add_argument('-d', '--debug', type=str, required=False, action=argparse.BooleanOptionalAction, help="debug") 181 | args = parser.parse_args() 182 | if args.id is None: 183 | print('use -i or --id argument') 184 | exit(0) 185 | if args.pwd is None: 186 | print('use -p or --pwd argument') 187 | exit(0) 188 | 189 | run(args.id, args.pwd, args.debug) 190 | -------------------------------------------------------------------------------- /run_new.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import hashlib 3 | import json 4 | import logging 5 | import os 6 | import time 7 | 8 | from selenium import webdriver 9 | from selenium.webdriver.chrome.service import Service 10 | from selenium.webdriver.common.by import By 11 | from webdriver_manager.chrome import ChromeDriverManager 12 | 13 | import naver_paper_clien as clien 14 | import naver_paper_damoang as damoang 15 | import naver_paper_ppomppu as ppomppu 16 | import naver_paper_ruliweb as ruliweb 17 | 18 | 19 | def grep_campaign_links(): 20 | campaign_links = [] 21 | campaign_links += clien.find_naver_campaign_links() 22 | campaign_links += damoang.find_naver_campaign_links() 23 | campaign_links += ppomppu.find_naver_campaign_links() 24 | campaign_links += ruliweb.find_naver_campaign_links() 25 | 26 | if(campaign_links == []): 27 | print("모든 링크를 방문했습니다.") 28 | exit() 29 | 30 | return set(campaign_links) 31 | 32 | 33 | def init(id, pwd, ua, mobile_device, headless, newsave): 34 | # 크롬 드라이버 옵션 설정 35 | chrome_options = webdriver.ChromeOptions() 36 | 37 | if headless is True: 38 | chrome_options.add_argument("--headless=new") 39 | user_dir = os.getcwd() + "/user_dir/" + hashlib.sha256(f"{id}_{pwd}_{ua}".encode('utf-8')).hexdigest() 40 | chrome_options.add_argument(f"--user-data-dir={user_dir}") 41 | if ua is not None: 42 | chrome_options.add_argument(f"--user-agent={ua}") 43 | 44 | # 새로운 창 생성 45 | driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=chrome_options) 46 | driver.get("https://nid.naver.com") 47 | 48 | # Login page (log-in required) title for nid.naver.com 49 | # Naver Sign in 50 | # ID page (successful logged-in) title for nid.naver.com 51 | # Naver ID 52 | if driver.title == "Naver ID" or driver.title == "네이버ID": 53 | return driver 54 | 55 | # 현재 열려 있는 창 가져오기 56 | current_window_handle = driver.current_window_handle 57 | 58 | # 새롭게 생성된 탭의 핸들을 찾습니다 59 | # 만일 새로운 탭이 없을경우 기존 탭을 사용합니다. 60 | new_window_handle = None 61 | for handle in driver.window_handles: 62 | if handle != current_window_handle: 63 | new_window_handle = handle 64 | break 65 | else: 66 | new_window_handle = handle 67 | 68 | # 새로운 탭을 driver2로 지정합니다 69 | driver.switch_to.window(new_window_handle) 70 | driver2 = driver 71 | 72 | username = driver2.find_element(By.NAME, 'id') 73 | pw = driver2.find_element(By.NAME, 'pw') 74 | 75 | # GitHub Action을 사용하지 않을 경우, 아래와 같이 변경 해주어야 합니다. 76 | input_id = id 77 | input_pw = pwd 78 | 79 | # ID input 클릭 80 | username.click() 81 | # js를 사용해서 붙여넣기 발동 <- 왜 일부러 이러냐면 pypyautogui랑 pyperclip를 사용해서 복붙 기능을 했는데 운영체제때문에 안되서 이렇게 한거다. 82 | driver2.execute_script("arguments[0].value = arguments[1]", username, input_id) 83 | time.sleep(1) 84 | 85 | pw.click() 86 | driver2.execute_script("arguments[0].value = arguments[1]", pw, input_pw) 87 | time.sleep(1) 88 | 89 | # Enable Stay Signed in 90 | if not driver2.find_element(By.CLASS_NAME, "input_keep").is_selected(): 91 | driver2.find_element(By.CLASS_NAME, "keep_text").click() 92 | time.sleep(1) 93 | 94 | # Enable IP Security 95 | if not driver2.find_element(By.CLASS_NAME, "switch_checkbox").is_selected(): 96 | driver2.find_element(By.CLASS_NAME, "switch_btn").click() 97 | time.sleep(1) 98 | 99 | # 입력을 완료하면 로그인 버튼 클릭 100 | driver2.find_element(By.CLASS_NAME, "btn_login").click() 101 | time.sleep(1) 102 | 103 | # new.save 등록 104 | # new.dontsave 등록 안함 105 | try: 106 | if newsave is True: 107 | driver2.find_element(By.ID, "new.save").click() 108 | else: 109 | driver2.find_element(By.ID, "new.dontsave").click() 110 | time.sleep(1) 111 | except Exception as e: 112 | # Print warning and go to login page. 113 | logging.warning("%s: new save or dontsave 오류", e) 114 | driver.get("https://nid.naver.com") 115 | 116 | try_login_limit = os.getenv("TRY_LOGIN", 3) 117 | try_login_count = 1 118 | while True: 119 | page_title = driver2.title 120 | if page_title == "Naver ID" or page_title == "네이버ID": 121 | break 122 | if try_login_count > try_login_limit: 123 | exit() 124 | print(f"로그인 되지 않음 #{try_login_count}") 125 | print(f"페이지 타이틀 : {page_title}") 126 | 127 | if headless is True: 128 | time.sleep(1) 129 | else: 130 | # Additional time for the user to address any login issues. 131 | time.sleep(30) 132 | try_login_count += 1 133 | 134 | return driver2 135 | 136 | def add_options_mobile_device(id, pwd, ua, mobile_device, headless, driver): 137 | # 현재 URL 저장 138 | current_url = driver.current_url 139 | 140 | # 새로운 Chrome 옵션 생성 141 | new_options = webdriver.ChromeOptions() 142 | 143 | # 기존 옵션 144 | if headless is True: 145 | new_options.add_argument("--headless=new") 146 | user_dir = os.getcwd() + "/user_dir/" + hashlib.sha256(f"{id}_{pwd}_{mobile_device}".encode('utf-8')).hexdigest() 147 | new_options.add_argument(f"--user-data-dir={user_dir}") 148 | if ua is not None: 149 | new_options.add_argument(f"--user-agent={ua}") 150 | 151 | # 모바일 에뮬레이션 추가 152 | if mobile_device: 153 | mobile_emulation = {"deviceName": mobile_device} 154 | new_options.add_experimental_option("mobileEmulation", mobile_emulation) 155 | 156 | # 새로운 드라이버 생성 157 | new_driver = webdriver.Chrome(options=new_options) 158 | 159 | # 쿠키 복사 160 | new_driver.get(current_url) 161 | for cookie in driver.get_cookies(): 162 | new_driver.add_cookie(cookie) 163 | 164 | # 기존 드라이버 종료 165 | driver.quit() 166 | return new_driver 167 | 168 | def alert_accept(alert, drvier): 169 | try: 170 | alert.accept() 171 | except: 172 | print("일반적인 방법으로 알럿을 닫을 수 없습니다. JavaScript를 사용해 닫기를 시도합니다.") 173 | drvier.execute_script("window.alert = function() {};") 174 | drvier.execute_script("window.confirm = function() { return true; };") 175 | 176 | def visit(campaign_links, driver2): 177 | campaign_links_len = len(campaign_links) 178 | for idx, link in enumerate(campaign_links): 179 | print(f"\r\n[{idx+1}/{campaign_links_len}] campaign link\t : {link}") 180 | try: 181 | # Send a request to the base URL 182 | driver2.get(link) 183 | result = driver2.switch_to.alert 184 | print(f"알럿창\r\n{result.text}") 185 | if (result.text == "적립 기간이 아닙니다." 186 | or result.text == "클릭적립은 캠페인당 1회만 적립됩니다."): 187 | alert_accept(result) 188 | else: 189 | time.sleep(4) 190 | alert_accept(result) 191 | except: 192 | try: 193 | div_dim = driver2.find_element('css selector', 'div.dim') 194 | print(f"레이어 알럿창\r\n{div_dim.text}") 195 | except: 196 | print(f"화면을 불러오지 못했거나 또는 클릭할 수 있는 내용 없음") 197 | # error_title = driver2.find_element('css selector', 'div.error_title') 198 | # print(f"{error_title.text}") 199 | pass 200 | time.sleep(3) 201 | # pageSource = driver2.page_source 202 | # print(pageSource) 203 | time.sleep(1) 204 | 205 | 206 | def main(campaign_links, id, pwd, ua, mobile_device, headless, newsave): 207 | driver = init(id, pwd, ua, mobile_device, headless, newsave) 208 | if mobile_device is not None: 209 | driver = add_options_mobile_device(id, pwd, ua, mobile_device, headless, driver) 210 | visit(campaign_links, driver) 211 | driver.quit() 212 | 213 | 214 | if __name__ == "__main__": 215 | parser = argparse.ArgumentParser() 216 | parser.add_argument('-i', '--id', type=str, required=False, help="naver id") 217 | parser.add_argument('-p', '--pw', type=str, required=False, help="naver password") 218 | parser.add_argument('-c', '--cd', type=str, required=False, help="credential json") 219 | parser.add_argument('--headless', type=bool, required=False, 220 | default=True, action=argparse.BooleanOptionalAction, 221 | help="browser headless mode (default: headless)") 222 | parser.add_argument('--newsave', type=bool, required=False, 223 | default=False, action=argparse.BooleanOptionalAction, 224 | help="new save or do not") 225 | parser.add_argument('-cf', '--credential-file', type=str, required=False, 226 | help="credential json file") 227 | args = parser.parse_args() 228 | cd_obj = None 229 | headless = args.headless 230 | newsave = args.newsave 231 | if (args.id is None and 232 | args.pw is None and 233 | args.cd is None and 234 | args.credential_file is None): 235 | id = os.getenv("USERNAME") 236 | pw = os.getenv("PASSWORD") 237 | cd_env = os.getenv("CREDENTIALENV", None) 238 | if(pw is None and pw is None and cd_env is None): 239 | print('not setting USERNAME / PASSWORD') 240 | exit() 241 | if cd_env is None and cd_env == "": 242 | cd_obj = [{"id": id, "pw": pw}] 243 | else: 244 | cd_obj = json.loads(cd_env) 245 | elif(args.cd is not None): 246 | try: 247 | cd_obj = json.loads(args.cd) 248 | except: 249 | print('use -c or --cd argument') 250 | print('credential json sample [{"id":"id1","pw":"pw1"},{"id":"id2","pw":"pw2"}]') 251 | print('json generate site https://jsoneditoronline.org/') 252 | exit() 253 | elif args.credential_file is not None: 254 | file_obj = open(args.credential_file, "r", encoding="utf-8") 255 | cd_obj = json.load(file_obj) 256 | else: 257 | if args.id is None: 258 | print('use -i or --id argument') 259 | exit() 260 | if args.pw is None: 261 | print('use -p or --pwd argument') 262 | exit() 263 | cd_obj = [{"id": args.id, "pw": args.pw}] 264 | 265 | campaign_links = grep_campaign_links() 266 | for idx, account in enumerate(cd_obj): 267 | id = account.get("id") 268 | pw = account.get("pw") 269 | ua = account.get("ua") 270 | mobile_device = account.get("mobile_device") 271 | 272 | print(f"\r\n>>> {idx+1}번째 계정") 273 | 274 | if id is None: 275 | print("ID not found!") 276 | continue 277 | if pw is None: 278 | print("PW not found!") 279 | continue 280 | 281 | main(campaign_links, id, pw, ua, mobile_device, headless, newsave) 282 | --------------------------------------------------------------------------------