﻿{"id":1121,"date":"2022-10-12T06:47:10","date_gmt":"2022-10-11T22:47:10","guid":{"rendered":"https:\/\/byy3.com\/?p=1121"},"modified":"2022-10-12T06:47:10","modified_gmt":"2022-10-11T22:47:10","slug":"google-ads-clicker-with-python-selenium-and-tor","status":"publish","type":"post","link":"https:\/\/byy3.com\/?p=1121","title":{"rendered":"Google Ads Clicker with Python, Selenium, and Tor"},"content":{"rendered":"<div class=\"iy iz ja jb jc\">\n<div class=\"\">\n<h1 id=\"4ca2\" class=\"pw-post-title jd je jf bm jg jh ji jj jk jl jm jn jo jp jq jr js jt ju jv jw jx jy jz ka kb fu\" data-selectable-paragraph=\"\">Tor<\/h1>\n<\/div>\n<div class=\"\">\n<h2 id=\"4d15\" class=\"pw-subtitle-paragraph kc je jf bm b kd ke kf kg kh ki kj kk kl km kn ko kp kq kr ks kt cn\" data-selectable-paragraph=\"\">How to click ads with different IPs.<\/h2>\n<\/div>\n<figure class=\"kv kw kx ky gr kz gf gg paragraph-image\">\n<div class=\"la lb do lc ce ld\" tabindex=\"0\" role=\"button\">\n<div class=\"gf gg ku\"><picture><source srcset=\"https:\/\/miro.medium.com\/max\/640\/1*OVOsyYVQMmnA9GHNV4eBcg.jpeg 640w, https:\/\/miro.medium.com\/max\/720\/1*OVOsyYVQMmnA9GHNV4eBcg.jpeg 720w, https:\/\/miro.medium.com\/max\/750\/1*OVOsyYVQMmnA9GHNV4eBcg.jpeg 750w, https:\/\/miro.medium.com\/max\/786\/1*OVOsyYVQMmnA9GHNV4eBcg.jpeg 786w, https:\/\/miro.medium.com\/max\/828\/1*OVOsyYVQMmnA9GHNV4eBcg.jpeg 828w, https:\/\/miro.medium.com\/max\/1100\/1*OVOsyYVQMmnA9GHNV4eBcg.jpeg 1100w, https:\/\/miro.medium.com\/max\/1400\/1*OVOsyYVQMmnA9GHNV4eBcg.jpeg 1400w\" sizes=\"(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 700px\" data-testid=\"og\" \/><img loading=\"lazy\" decoding=\"async\" class=\"ce le lf c\" role=\"presentation\" data-original=\"https:\/\/miro.medium.com\/max\/875\/1*OVOsyYVQMmnA9GHNV4eBcg.jpeg\" src=\"https:\/\/byy3.com\/wp-content\/themes\/MNews%20V2.4\/images\/post-loading.gif\" width=\"700\" height=\"434\" title=\"Google Ads Clicker with Python, Selenium, and Tor\u63d2\u56fe\" alt=\"Google Ads Clicker with Python, Selenium, and Tor\u63d2\u56fe\" \/><\/picture><\/div>\n<\/div><figcaption class=\"lg bl gh gf gg lh li bm b bn bo cn\" data-selectable-paragraph=\"\">Photo by\u00a0<a class=\"au lj\" href=\"https:\/\/byy3.com\/go\/?url=https:\/\/www.pexels.com\/@pixabay?utm_content=attributionCopyText&amp;utm_medium=referral&amp;utm_source=pexels\" target=\"_blank\" rel=\"noopener ugc nofollow\" rel=\"nofollow\" >Pixabay<\/a>\u00a0from\u00a0<a class=\"au lj\" href=\"https:\/\/byy3.com\/go\/?url=https:\/\/www.pexels.com\/photo\/google-search-engine-on-macbook-pro-40185\/?utm_content=attributionCopyText&amp;utm_medium=referral&amp;utm_source=pexels\" target=\"_blank\" rel=\"noopener ugc nofollow\" rel=\"nofollow\" >Pexels<\/a><\/figcaption><\/figure>\n<p id=\"e3bc\" class=\"pw-post-body-paragraph lk ll jf lm b ln lo kg lp lq lr kj ls lt lu lv lw lx ly lz ma mb mc md me mf iy fu\" data-selectable-paragraph=\"\">Recently, someone from a local freelancing platform asked me to write a program to click ads for a given search query on Google. I sent him a sample but he decided not to buy it, so I decided to publish it here.<\/p>\n<p id=\"a15d\" class=\"pw-post-body-paragraph lk ll jf lm b ln lo kg lp lq lr kj ls lt lu lv lw lx ly lz ma mb mc md me mf iy fu\" data-selectable-paragraph=\"\">It uses the Tor network to change the IP address and Selenium with Python bindings for browser automation.<\/p>\n<\/div>\n<div class=\"o dx mg mh ih mi\" role=\"separator\"><\/div>\n<div class=\"iy iz ja jb jc\">\n<h2 id=\"dc5c\" class=\"mn mo jf bm mp mq mr ms mt mu mv mw mx lt my mz na lx nb nc nd mb ne nf ng nh fu\" data-selectable-paragraph=\"\">How to Setup Environment and Tor<\/h2>\n<p id=\"4f24\" class=\"pw-post-body-paragraph lk ll jf lm b ln ni kg lp lq nj kj ls lt nk lv lw lx nl lz ma mb nm md me mf iy fu\" data-selectable-paragraph=\"\">You can follow the steps manually from\u00a0<a class=\"au lj\" href=\"https:\/\/byy3.com\/go\/?url=https:\/\/gist.github.com\/DusanMadar\/8d11026b7ce0bce6a67f7dd87b999f6b\" target=\"_blank\" rel=\"noopener ugc nofollow\" rel=\"nofollow\" >this gist<\/a>\u00a0to set up Tor or use the following script to set up both virtual environment and Tor.<\/p>\n<figure class=\"kv kw kx ky gr kz\">\n<div class=\"m fm l do\">\n<div class=\"yj no l\">\n<pre>#!\/bin\/bash\r\n\r\nproject_dir=$(pwd)\r\n\r\n# get password for Tor setup\r\ntor_pwd=\"\"\r\necho \"Enter a password for tor network setup\"\r\nread tor_pwd\r\n\r\necho \"Setting environment variable TOR_PWD...\"\r\necho \"export TOR_PWD=$tor_pwd\" &gt;&gt; ~\/.bashrc\r\nsource ~\/.bashrc\r\ncd $project_dir\r\n\r\necho \"Installing Tor package...\"\r\nsudo apt update\r\nsudo apt install -y tor\r\n\r\necho \"Installing privoxy package...\"\r\nsudo apt install -y privoxy\r\n\r\n# create virtual environment and activate\r\necho \"Creating virtual environment...\"\r\npython -m venv env\r\nsource env\/bin\/activate\r\n\r\n# install requirements\r\necho \"Installing required packages...\"\r\npip install wheel\r\npip install -r requirements.txt\r\n\r\n# run as root\r\nsudo su - &lt;&lt;EOF\r\necho \"Making changes as root...\"\r\n\r\nport_enabled=$(egrep \"^ControlPort 9051\" \/etc\/tor\/torrc)\r\nif [[ -z \"${port_enabled}\" ]]; then\r\n echo \"Enabling control port...\"\r\n echo \"ControlPort 9051\" &gt;&gt; \/etc\/tor\/torrc\r\nfi\r\n\r\necho \"Setting hashed Tor password...\"\r\necho HashedControlPassword $(tor --hash-password \"${tor_pwd}\" | tail -n 1) &gt;&gt; \/etc\/tor\/torrc\r\n\r\necho \"Starting Tor service...\"\r\nservice tor restart\r\n\r\necho \"Setting privoxy config...\"\r\necho \"forward-socks5t \/ 127.0.0.1:9050 .\" &gt;&gt; \/etc\/privoxy\/config\r\nservice privoxy start\r\nEOF\r\n\r\necho \"::::: Setup Completed :::::\"\r\n\r\n# add run command as help\r\necho -e \"\\nRun the following commands to start\"\r\necho \"source env\/bin\/activate\"\r\necho \"python ad_clicker.py -q &lt;search keywords&gt;\"<\/pre>\n<\/div>\n<\/div>\n<\/figure>\n<p id=\"9a69\" class=\"pw-post-body-paragraph lk ll jf lm b ln lo kg lp lq lr kj ls lt lu lv lw lx ly lz ma mb mc md me mf iy fu\" data-selectable-paragraph=\"\">You should see the following output at the end of the run.<\/p>\n<pre class=\"kv kw kx ky gr np bs nq\"><span id=\"b5d8\" class=\"fu mn mo jf nr b dm ns nt l nu\" data-selectable-paragraph=\"\">...\r\nEnabling control port...\r\nSetting hashed Tor password...\r\nStarting Tor service...\r\n::::: Setup Completed :::::<\/span><span id=\"12b5\" class=\"fu mn mo jf nr b dm nv nw nx ny nz nt l nu\" data-selectable-paragraph=\"\">Run the following commands to start\r\nsource env\/bin\/activate\r\npython ad_clicker.py -q &lt;search keywords&gt;<\/span><\/pre>\n<p id=\"22a5\" class=\"pw-post-body-paragraph lk ll jf lm b ln lo kg lp lq lr kj ls lt lu lv lw lx ly lz ma mb mc md me mf iy fu\" data-selectable-paragraph=\"\">Since your environment and Tor setup is ready at this point, we can continue with the implementation.<\/p>\n<\/div>\n<div class=\"o dx mg mh ih mi\" role=\"separator\"><\/div>\n<div class=\"iy iz ja jb jc\">\n<h2 id=\"6735\" class=\"mn mo jf bm mp mq mr ms mt mu mv mw mx lt my mz na lx nb nc nd mb ne nf ng nh fu\" data-selectable-paragraph=\"\">How to Implement the Ad Clicker Automation<\/h2>\n<p id=\"89c2\" class=\"pw-post-body-paragraph lk ll jf lm b ln ni kg lp lq nj kj ls lt nk lv lw lx nl lz ma mb nm md me mf iy fu\" data-selectable-paragraph=\"\">After setting up Tor, it is easy to change your IP within Python code using the\u00a0<code class=\"fj oa ob oc nr b\">stem<\/code>\u00a0package.<\/p>\n<figure class=\"kv kw kx ky gr kz\">\n<div class=\"m fm l do\">\n<div class=\"abf no l\">\n<pre>from stem import Signal\r\nfrom stem.control import Controller\r\n\r\n\r\ndef change_ip_address(password):\r\n \"\"\"Change IP address over Tor connection\r\n\r\n :type password: str\r\n :param password: Tor authentication password\r\n \"\"\"\r\n\r\n logger.info(\"Changing ip address...\")\r\n with Controller.from_port(port=9051) as controller:\r\n controller.authenticate(password=password)\r\n controller.signal(Signal.NEWNYM)<\/pre>\n<\/div>\n<\/div>\n<\/figure>\n<p id=\"a180\" class=\"pw-post-body-paragraph lk ll jf lm b ln lo kg lp lq lr kj ls lt lu lv lw lx ly lz ma mb mc md me mf iy fu\" data-selectable-paragraph=\"\">The script requires a query parameter with the\u00a0<code class=\"fj oa ob oc nr b\">-q<\/code>\u00a0option. If you used the\u00a0<code class=\"fj oa ob oc nr b\">ad_clicker_setup.sh<\/code>\u00a0script, the Tor authentication password will be received from the\u00a0<code class=\"fj oa ob oc nr b\">TOR_PWD<\/code>\u00a0environment variable. Otherwise, the user will be asked to type the password determined at the setup phase.<\/p>\n<p id=\"786b\" class=\"pw-post-body-paragraph lk ll jf lm b ln lo kg lp lq lr kj ls lt lu lv lw lx ly lz ma mb mc md me mf iy fu\" data-selectable-paragraph=\"\">After IP is changed, a search on Google will be initiated for the given query. If there are ads in the results, they will be clicked in order.<\/p>\n<figure class=\"kv kw kx ky gr kz\">\n<div class=\"m fm l do\">\n<div class=\"abg no l\">\n<pre>def main():\r\n\r\n arg_parser = get_arg_parser()\r\n args = arg_parser.parse_args()\r\n\r\n if not args.query:\r\n logger.error(\"Run with search query!\")\r\n arg_parser.print_help()\r\n raise SystemExit()\r\n\r\n os.environ[\"WDM_LOG_LEVEL\"] = \"0\"\r\n password = os.environ.get(\"TOR_PWD\", None)\r\n\r\n if not password:\r\n password = getpass.getpass(\"Enter tor password: \")\r\n\r\n change_ip_address(password)\r\n\r\n response = requests.get(\"https:\/\/api.myip.com\", proxies={\"https\": \"socks5h:\/\/127.0.0.1:9050\"})\r\n logger.info(f\"Connecting with IP: {response.json()['ip']}\")\r\n\r\n search_controller = SearchController(args.query)\r\n ads = search_controller.search_for_ads()\r\n\r\n if not ads:\r\n logger.info(\"No ads in the search results!\")\r\n else:\r\n logger.info(f\"Found {len(ads)} ads\")\r\n search_controller.click_ads(ads)\r\n search_controller.end_search()\r\n\r\n\r\nif __name__ == \"__main__\":\r\n\r\n main()<\/pre>\n<\/div>\n<\/div>\n<\/figure>\n<p id=\"80e2\" class=\"pw-post-body-paragraph lk ll jf lm b ln lo kg lp lq lr kj ls lt lu lv lw lx ly lz ma mb mc md me mf iy fu\" data-selectable-paragraph=\"\">The\u00a0<code class=\"fj oa ob oc nr b\">SearchController<\/code>\u00a0class is the actual module that does the work. It starts the search and performs clicks for ads found.<\/p>\n<figure class=\"kv kw kx ky gr kz\">\n<div class=\"m fm l do\">\n<div class=\"abh no l\">\n<pre>def search_for_ads(self):\r\n \"\"\"Start search for the given query and return ads if any\r\n\r\n :rtype: list\r\n :returns: List of (ad, ad_link) tuples\r\n \"\"\"\r\n\r\n logger.info(f\"Starting search for {self._search_query}\")\r\n\r\n search_input_box = self._driver.find_element(*self.SEARCH_INPUT)\r\n search_input_box.send_keys(self._search_query, Keys.ENTER)\r\n\r\n ad_links = []\r\n\r\n try:\r\n wait = WebDriverWait(self._driver, timeout=10)\r\n results_loaded = wait.until(EC.presence_of_element_located((By.ID, \"result-stats\")))\r\n\r\n if results_loaded:\r\n logger.info(\"Getting ad links...\")\r\n ad_links = self._get_ad_links()\r\n\r\n except TimeoutException:\r\n logger.error(\"Timed out waiting for results!\")\r\n self.end_search()\r\n\r\n return ad_links<\/pre>\n<\/div>\n<\/div>\n<\/figure>\n<p id=\"0b1a\" class=\"pw-post-body-paragraph lk ll jf lm b ln lo kg lp lq lr kj ls lt lu lv lw lx ly lz ma mb mc md me mf iy fu\" data-selectable-paragraph=\"\">The\u00a0<code class=\"fj oa ob oc nr b\">search_for_ads<\/code>\u00a0method first locates the search box and enters the search query. After results loaded, the\u00a0<code class=\"fj oa ob oc nr b\">_get_ad_links<\/code>\u00a0method locates all ads under the element with id\u00a0<em class=\"od\">tads,\u00a0<\/em>and found ads are returned<em class=\"od\">.<\/em><\/p>\n<p id=\"7bab\" class=\"pw-post-body-paragraph lk ll jf lm b ln lo kg lp lq lr kj ls lt lu lv lw lx ly lz ma mb mc md me mf iy fu\" data-selectable-paragraph=\"\">After ads are found, the\u00a0<code class=\"fj oa ob oc nr b\">click_ads<\/code>\u00a0method opens them in a different tab by sending\u00a0<code class=\"fj oa ob oc nr b\">Control<\/code>\u00a0and left mouse key to the ad link element.<\/p>\n<figure class=\"kv kw kx ky gr kz\">\n<div class=\"m fm l do\">\n<div class=\"abi no l\">\n<pre>def click_ads(self, ads):\r\n \"\"\"Click ads found\r\n\r\n :type ads: list\r\n :param ads: List of (ad, ad_link) tuples\r\n \"\"\"\r\n\r\n # store the ID of the original window\r\n original_window_handle = self._driver.current_window_handle\r\n\r\n for ad in ads:\r\n ad_link_element = ad[0]\r\n ad_link = ad[1]\r\n logger.info(f\"Clicking {ad_link}...\")\r\n\r\n # open link in a different tab\r\n ad_link_element.send_keys(Keys.CONTROL + Keys.RETURN)\r\n\r\n for window_handle in self._driver.window_handles:\r\n if window_handle != original_window_handle:\r\n self._driver.switch_to.window(window_handle)\r\n sleep(4)\r\n self._driver.close()\r\n break\r\n\r\n # go back to original window\r\n self._driver.switch_to.window(original_window_handle)\r\n sleep(1)\r\n\r\n # scroll the page to avoid elements remain outside of the view\r\n self._driver.execute_script(\"arguments[0].scrollIntoView(true);\", ad_link_element)<\/pre>\n<\/div>\n<\/div>\n<\/figure>\n<p id=\"ada1\" class=\"pw-post-body-paragraph lk ll jf lm b ln lo kg lp lq lr kj ls lt lu lv lw lx ly lz ma mb mc md me mf iy fu\" data-selectable-paragraph=\"\">After clicking all the ads, the\u00a0<code class=\"fj oa ob oc nr b\">end_search<\/code>\u00a0method closes the browser.<\/p>\n<figure class=\"kv kw kx ky gr kz\">\n<div class=\"m fm l do\">\n<div class=\"abj no l\">\n<pre>def end_search(self):\r\n \"\"\"Close the browser\"\"\"\r\n\r\n self._driver.quit()<\/pre>\n<\/div>\n<\/div>\n<\/figure>\n<p id=\"dbd2\" class=\"pw-post-body-paragraph lk ll jf lm b ln lo kg lp lq lr kj ls lt lu lv lw lx ly lz ma mb mc md me mf iy fu\" data-selectable-paragraph=\"\">You can find the project\u00a0<a class=\"au lj\" href=\"https:\/\/byy3.com\/go\/?url=https:\/\/github.com\/coskundeniz\/ad_clicker\" target=\"_blank\" rel=\"noopener ugc nofollow\" rel=\"nofollow\" >here<\/a>.<\/p>\n<figure class=\"kv kw kx ky gr kz gf gg paragraph-image\">\n<div class=\"la lb do lc ce ld\" tabindex=\"0\" role=\"button\">\n<div class=\"gf gg oe\"><picture><source srcset=\"https:\/\/miro.medium.com\/max\/640\/1*hpyg34TBBqa7nU-EYPx-dQ.gif 640w, https:\/\/miro.medium.com\/max\/720\/1*hpyg34TBBqa7nU-EYPx-dQ.gif 720w, https:\/\/miro.medium.com\/max\/750\/1*hpyg34TBBqa7nU-EYPx-dQ.gif 750w, https:\/\/miro.medium.com\/max\/786\/1*hpyg34TBBqa7nU-EYPx-dQ.gif 786w, https:\/\/miro.medium.com\/max\/828\/1*hpyg34TBBqa7nU-EYPx-dQ.gif 828w, https:\/\/miro.medium.com\/max\/1100\/1*hpyg34TBBqa7nU-EYPx-dQ.gif 1100w, https:\/\/miro.medium.com\/max\/1400\/1*hpyg34TBBqa7nU-EYPx-dQ.gif 1400w\" sizes=\"(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 700px\" data-testid=\"og\" \/><img loading=\"lazy\" decoding=\"async\" class=\"ce le lf c\" role=\"presentation\" data-original=\"https:\/\/miro.medium.com\/max\/875\/1*hpyg34TBBqa7nU-EYPx-dQ.gif\" src=\"https:\/\/byy3.com\/wp-content\/themes\/MNews%20V2.4\/images\/post-loading.gif\" width=\"700\" height=\"367\" title=\"Google Ads Clicker with Python, Selenium, and Tor\u63d2\u56fe1\" alt=\"Google Ads Clicker with Python, Selenium, and Tor\u63d2\u56fe1\" \/><\/picture><\/div>\n<\/div><figcaption class=\"lg bl gh gf gg lh li bm b bn bo cn\" data-selectable-paragraph=\"\">Execution of the ad_clicker.py<\/figcaption><\/figure>\n<p id=\"ecd1\" class=\"pw-post-body-paragraph lk ll jf lm b ln lo kg lp lq lr kj ls lt lu lv lw lx ly lz ma mb mc md me mf iy fu\" data-selectable-paragraph=\"\">Thank you for your time.<\/p>\n<\/div>\n<div class=\"o dx mg mh ih mi\" role=\"separator\"><\/div>\n","protected":false},"excerpt":{"rendered":"<p>Tor How to click ads with different IPs. Photo by\u00a0Pixab [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[20,1,21],"tags":[226,744,747,746,745,225],"class_list":["post-1121","post","type-post","status-publish","format-standard","hentry","category-python","category-net-security","category-script","tag-google","tag-google-ads","tag-google-ads-click","tag-google-click","tag-google-search","tag-tor"],"_links":{"self":[{"href":"https:\/\/byy3.com\/index.php?rest_route=\/wp\/v2\/posts\/1121","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/byy3.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/byy3.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/byy3.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/byy3.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1121"}],"version-history":[{"count":0,"href":"https:\/\/byy3.com\/index.php?rest_route=\/wp\/v2\/posts\/1121\/revisions"}],"wp:attachment":[{"href":"https:\/\/byy3.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1121"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/byy3.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1121"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/byy3.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1121"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}