├── .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 | [](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 |
--------------------------------------------------------------------------------