win scraping

- will scape won games at start to update db for giveaways the bot has entered
- schedules a once a day run of scraping won giveaways
This commit is contained in:
mcinj 2022-06-08 21:55:17 -04:00
parent de2379587e
commit 48c030f445
10 changed files with 200 additions and 13 deletions

View file

@ -2,10 +2,6 @@ requests==2.27.1
beautifulsoup4==4.11.1 beautifulsoup4==4.11.1
urllib3==1.26.9 urllib3==1.26.9
sqlalchemy==1.4.36 sqlalchemy==1.4.36
sqlalchemy_utils==0.38.2
paginate-sqlalchemy==0.3.1
alembic==1.7.7 alembic==1.7.7
python-dateutil==2.8.2 python-dateutil==2.8.2
Flask==2.1.2 apscheduler==3.9.1
Flask-BasicAuth==0.2.0
pyopenssl==22.0.0

View file

@ -11,7 +11,7 @@ from sqlalchemy.orm import Session, joinedload
from sqlalchemy_utils import database_exists from sqlalchemy_utils import database_exists
from .log import get_logger from .log import get_logger
from .models import TableNotification, TableGiveaway, TableSteamItem, TableTask from .models import TableNotification, TableGiveaway, TableSteamItem
logger = get_logger(__name__) logger = get_logger(__name__)
engine = sqlalchemy.create_engine(f"{os.getenv('BOT_DB_URL', 'sqlite:///./config/sqlite.db')}", echo=False) engine = sqlalchemy.create_engine(f"{os.getenv('BOT_DB_URL', 'sqlite:///./config/sqlite.db')}", echo=False)
@ -116,6 +116,16 @@ class GiveawayHelper:
with Session(engine) as session: with Session(engine) as session:
return session.query(TableGiveaway).filter_by(entered=True).count() return session.query(TableGiveaway).filter_by(entered=True).count()
@classmethod
def total_won(cls):
with Session(engine) as session:
return session.query(TableGiveaway).filter_by(won=True).count()
@classmethod
def get_by_giveaway_id(cls, game_id):
with Session(engine) as session:
return session.query(TableGiveaway).filter_by(giveaway_id=game_id).one_or_none()
@classmethod @classmethod
def unix_timestamp_to_utc_datetime(cls, timestamp): def unix_timestamp_to_utc_datetime(cls, timestamp):
return datetime.utcfromtimestamp(timestamp) return datetime.utcfromtimestamp(timestamp)
@ -126,6 +136,17 @@ class GiveawayHelper:
return session.query(TableGiveaway).filter_by(giveaway_id=giveaway.giveaway_game_id, return session.query(TableGiveaway).filter_by(giveaway_id=giveaway.giveaway_game_id,
steam_id=giveaway.steam_app_id).all() steam_id=giveaway.steam_app_id).all()
@classmethod
def mark_game_as_won(cls, game_id):
with Session(engine) as session:
result = session.query(TableGiveaway).filter_by(giveaway_id=game_id).all()
if result:
won_giveaway = result[0]
if not won_giveaway.won:
won_giveaway.won = True
session.add(won_giveaway)
session.commit()
@classmethod @classmethod
def insert(cls, giveaway, entered, won): def insert(cls, giveaway, entered, won):
with Session(engine) as session: with Session(engine) as session:
@ -149,7 +170,7 @@ class GiveawayHelper:
giveaway_ended_at=GiveawayHelper.unix_timestamp_to_utc_datetime(giveaway.time_remaining_timestamp), giveaway_ended_at=GiveawayHelper.unix_timestamp_to_utc_datetime(giveaway.time_remaining_timestamp),
cost=giveaway.cost, cost=giveaway.cost,
copies=giveaway.copies, copies=giveaway.copies,
contributor_level=giveaway._contributor_level, contributor_level=giveaway.contributor_level,
entered=entered, entered=entered,
won=won, won=won,
game_entries=giveaway.game_entries) game_entries=giveaway.game_entries)
@ -157,7 +178,7 @@ class GiveawayHelper:
session.commit() session.commit()
@classmethod @classmethod
def upsert_giveaway(cls, giveaway, entered, won): def upsert_giveaway_with_details(cls, giveaway, entered, won):
result = GiveawayHelper.get_by_ids(giveaway) result = GiveawayHelper.get_by_ids(giveaway)
if not result: if not result:
GiveawayHelper.insert(giveaway, entered, won) GiveawayHelper.insert(giveaway, entered, won)
@ -172,7 +193,7 @@ class GiveawayHelper:
giveaway_ended_at=GiveawayHelper.unix_timestamp_to_utc_datetime(giveaway.time_remaining_timestamp), giveaway_ended_at=GiveawayHelper.unix_timestamp_to_utc_datetime(giveaway.time_remaining_timestamp),
cost=giveaway.cost, cost=giveaway.cost,
copies=giveaway.copies, copies=giveaway.copies,
contributor_level=giveaway._contributor_level, contributor_level=giveaway.contributor_level,
entered=entered, entered=entered,
won=won, won=won,
game_entries=giveaway.game_entries) game_entries=giveaway.game_entries)
@ -195,7 +216,7 @@ class GiveawayHelper:
giveaway_ended_at=GiveawayHelper.unix_timestamp_to_utc_datetime(giveaway.time_remaining_timestamp), giveaway_ended_at=GiveawayHelper.unix_timestamp_to_utc_datetime(giveaway.time_remaining_timestamp),
cost=giveaway.cost, cost=giveaway.cost,
copies=giveaway.copies, copies=giveaway.copies,
contributor_level=giveaway._contributor_level, contributor_level=giveaway.contributor_level,
game_entries=giveaway.game_entries) game_entries=giveaway.game_entries)
session.merge(g) session.merge(g)
session.commit() session.commit()

View file

@ -223,13 +223,13 @@ class EnterGiveaways:
if if_enter_giveaway: if if_enter_giveaway:
res = self._enter_giveaway(giveaway) res = self._enter_giveaway(giveaway)
if res: if res:
GiveawayHelper.upsert_giveaway(giveaway, True, False) GiveawayHelper.upsert_giveaway_with_details(giveaway, True, False)
self._points -= int(giveaway.cost) self._points -= int(giveaway.cost)
txt = f"✅ Entered giveaway '{giveaway.game_name}'" txt = f"✅ Entered giveaway '{giveaway.game_name}'"
logger.info(txt) logger.info(txt)
sleep(randint(4, 15)) sleep(randint(4, 15))
else: else:
GiveawayHelper.upsert_giveaway(giveaway, False, False) GiveawayHelper.upsert_giveaway_with_details(giveaway, False, False)
else: else:
GiveawayHelper.upsert_giveaway(giveaway) GiveawayHelper.upsert_giveaway(giveaway)
# if we are on any filter type except New and we get to a giveaway that exceeds our # if we are on any filter type except New and we get to a giveaway that exceeds our

View file

@ -0,0 +1,90 @@
import json
from random import randint
from time import sleep
import requests
from bs4 import BeautifulSoup
from requests.adapters import HTTPAdapter
from urllib3.util import Retry
from .log import get_logger
from .database import NotificationHelper, GiveawayHelper
from .won_entry import WonEntry
logger = get_logger(__name__)
class SteamGiftsException(Exception):
pass
class EvaluateWonGiveaways:
def __init__(self, cookie, user_agent, notification):
self._contributor_level = None
self._xsrf_token = None
self._cookie = {
'PHPSESSID': cookie
}
self._user_agent = user_agent
self._notification = notification
self._base = "https://www.steamgifts.com/giveaways/won"
self._session = requests.Session()
def start(self):
self._evaluate_won_giveaways()
def _requests_retry_session(
self,
retries=5,
backoff_factor=0.3
):
session = self._session or requests.Session()
retry = Retry(
total=retries,
read=retries,
connect=retries,
backoff_factor=backoff_factor,
status_forcelist=(500, 502, 504),
)
adapter = HTTPAdapter(max_retries=retry)
session.mount('http://', adapter)
session.mount('https://', adapter)
return session
def _get_soup_from_page(self, url):
headers = {
'User-Agent': self._user_agent
}
self._requests_retry_session().get(url, headers=headers)
r = requests.get(url, cookies=self._cookie)
soup = BeautifulSoup(r.text, 'html.parser')
return soup
def _evaluate_won_giveaways(self, page=1):
soup = self._get_soup_from_page(self._base)
try:
self._xsrf_token = soup.find('input', {'name': 'xsrf_token'})['value']
self._points = int(soup.find('span', {'class': 'nav__points'}).text) # storage points
self._contributor_level = int(float(soup.select_one('nav a>span[title]')['title']))
except TypeError:
logger.error("⛔⛔⛔ Cookie is not valid. A new one must be added.⛔⛔⛔")
raise SteamGiftsException("Cookie is not valid. A new one must be added.")
won_game_list = soup.select('div[class=table__row-inner-wrap]')
if not len(won_game_list):
txt = f"🟡 No won games to evaluate"
logger.info(txt)
logger.info("Evaluating won giveaways")
for item in won_game_list:
won_giveaway = WonEntry(item)
w = GiveawayHelper.get_by_giveaway_id(won_giveaway.giveaway_game_id)
logger.debug(f"Giveaway in db: {w}")
if w and not w.won and w.entered:
logger.info(f"Found new won giveaway not already marked as won. Marking. {w}")
GiveawayHelper.mark_game_as_won(won_giveaway.giveaway_game_id)

View file

@ -7,8 +7,10 @@ from time import sleep
from dateutil import tz from dateutil import tz
from .evaluate_won_giveaways import EvaluateWonGiveaways
from .log import get_logger from .log import get_logger
from .enter_giveaways import EnterGiveaways from .enter_giveaways import EnterGiveaways
from .scheduler import Scheduler
logger = get_logger(__name__) logger = get_logger(__name__)
@ -47,6 +49,17 @@ class GiveawayThread(threading.Thread):
sleep(10) sleep(10)
exit(-1) exit(-1)
logger.debug("Creating scheduler")
scheduler = Scheduler()
won_giveaway_job = scheduler.get_job(job_id='eval_giveaways')
if won_giveaway_job:
logger.debug("Previous won giveaway evaluator job exists. Removing.")
won_giveaway_job.remove()
scheduler.add_job(EvaluateWonGiveaways(cookie, user_agent, notification).start,
id='eval_giveaways', trigger='interval', max_instances=1, replace_existing=True, days=1,
next_run_time=datetime.now() + timedelta(minutes=1))
scheduler.start()
while True: while True:
logger.info("🟢 Evaluating giveaways.") logger.info("🟢 Evaluating giveaways.")
if wishlist_page_enabled: if wishlist_page_enabled:

View file

@ -19,6 +19,9 @@ class TableNotification(Base):
__mapper_args__ = {"eager_defaults": True} __mapper_args__ = {"eager_defaults": True}
def __str__(self):
return str(self.__class__) + ": " + str(self.__dict__)
class TableSteamItem(Base): class TableSteamItem(Base):
__tablename__ = 'steam_item' __tablename__ = 'steam_item'
@ -30,6 +33,9 @@ class TableSteamItem(Base):
giveaways = relationship("TableGiveaway", back_populates="steam_item") giveaways = relationship("TableGiveaway", back_populates="steam_item")
def __str__(self):
return str(self.__class__) + ": " + str(self.__dict__)
class TableGiveaway(Base): class TableGiveaway(Base):
__tablename__ = 'giveaway' __tablename__ = 'giveaway'
@ -43,6 +49,7 @@ class TableGiveaway(Base):
copies = Column(Integer(), nullable=False) copies = Column(Integer(), nullable=False)
contributor_level = Column(Integer(), nullable=False) contributor_level = Column(Integer(), nullable=False)
entered = Column(Boolean(), nullable=False) entered = Column(Boolean(), nullable=False)
won = Column(Boolean(), nullable=False)
game_entries = Column(Integer(), nullable=False) game_entries = Column(Integer(), nullable=False)
created_at = Column(DateTime(timezone=True), server_default=func.now()) created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now()) updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
@ -50,3 +57,6 @@ class TableGiveaway(Base):
steam_item = relationship("TableSteamItem", back_populates="giveaways") steam_item = relationship("TableSteamItem", back_populates="giveaways")
__mapper_args__ = {"eager_defaults": True} __mapper_args__ = {"eager_defaults": True}
def __str__(self):
return str(self.__class__) + ": " + str(self.__dict__)

33
src/bot/scheduler.py Normal file
View file

@ -0,0 +1,33 @@
import os
from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore
from apscheduler.schedulers.background import BackgroundScheduler
from .log import get_logger
logger = get_logger(__name__)
class Scheduler:
class __Scheduler:
def __init__(self):
jobstores = {
'default': SQLAlchemyJobStore(url=f"{os.getenv('BOT_DB_URL', 'sqlite:///./config/sqlite.db')}")
}
job_defaults = {
'coalesce': True,
'max_instances': 3
}
self.scheduler = BackgroundScheduler(jobstores=jobstores, job_defaults=job_defaults)
def __getattr__(self, name):
return getattr(self.scheduler, name)
instance = None
def __init__(self):
if not Scheduler.instance:
Scheduler.instance = Scheduler.__Scheduler()
def __getattr__(self, name):
return getattr(self.instance, name)

22
src/bot/won_entry.py Normal file
View file

@ -0,0 +1,22 @@
import re
from .log import get_logger
import time
logger = get_logger(__name__)
class WonEntry:
def __init__(self, soup_item):
self.game_name = None
self.giveaway_game_id = None
self.giveaway_uri = None
logger.debug(f"Won Giveaway html: {soup_item}")
self.game_name = soup_item.find('a', {'class': 'table__column__heading'}).text
self.giveaway_game_id = soup_item.find('a', {'class': 'table__column__heading'})['href'].split('/')[2]
self.giveaway_uri = soup_item.select_one('a.table__column__heading')['href']
logger.debug(f"Scraped Won Giveaway: {self}")
def __str__(self):
return str(self.__class__) + ": " + str(self.__dict__)

View file

@ -5,5 +5,6 @@
<ul> <ul>
<li>Total Giveaways Considered: {{totals}}</li> <li>Total Giveaways Considered: {{totals}}</li>
<li>Giveaways Entered: {{entered}}</li> <li>Giveaways Entered: {{entered}}</li>
<li>Giveaways Won (with the bot): {{won}}</li>
</ul> </ul>
{% endblock %} {% endblock %}

View file

@ -74,7 +74,8 @@ class WebServerThread(threading.Thread):
def stats(): def stats():
totals = GiveawayHelper.total_giveaways() totals = GiveawayHelper.total_giveaways()
entered = GiveawayHelper.total_entered() entered = GiveawayHelper.total_entered()
return render_template('stats.html', name=self.prefix, totals=totals, entered=entered) won = GiveawayHelper.total_won()
return render_template('stats.html', name=self.prefix, totals=totals, entered=entered, won=won)
if self.enabled: if self.enabled:
logger.info("Webserver Enabled. Running") logger.info("Webserver Enabled. Running")